DbContext corrupts attached entities: why? - c#

I've got some code like this:
Activity[] GetAllActivities() {
using (ScheduleContext ctx = new ScheduleContext())
return ctx.Activities.AsNoTracking().ToArray();
}
The aim to have a very simple in-memory cache of some data: Activities is mapped to a db view which summarizes everything I need.
If I omit AsNoTracking the returned objects are non-deterministically corrupted: properties on the returned objects aren't set correctly, and frequently one object's property value is duplicated in other objects' properties. There's no warning or exception; neither on EF4.3.1 nor EF5rc2. Both CLR 4 and the 4.5 release candidate exhibit the same behavior.
The Activity objects are very simple; consisting solely of non-virtual properties of basic type (int, string, etc.) and have no key nor a relationship with any other entity.
Is this expected behavior? Where can I find documentation about this?
I understand that obviously change tracking cannot work once the relevant DbContext is gone, but I'm surprised the materialized properties are corrupted without warning. I'm mostly worried that I'll forget AsNoTracking somewhere in a more complex scenario and get somewhat plausible but wrong results.
Edit: The entity looks as follows. Thanks Jonathan & Kristof; there is indeed a column that is inferred as the ID!
public class Activity
{
public string ActivityHostKey { get; set; }
public int ActivityDuration { get; set; }
public int ActivityLastedChanged { get; set; }
public string ActivityId { get; set; }//!!!
public string ModuleHostKey { get; set; }
public string ModuleName { get; set; }
...

I think "frequently one object's property value is duplicated in other objects' properties" and that the Activity objects "and have no key" are the key pieces of information here (no pun intended).
When importing a View (which obviously doesn't have a primary key), EF guesses at what the primary key is. If tracking is then enabled, it uses that primary key to make sure only a single copy of each entity is created in memory. This means if you load two rows with the same values for the field EF guessed was the PK, the values for the second row will overwrite the first.
As for the data being "non-deterministically corrupted", that's probably because the database doesn't guarantee the order the rows are returned in, and it's a "last-in-wins" process in EF, so if the order of the records changes from the DB, the record that gets to keep it's values changes too.
Try marking more columns as part of the primary key, or modifying the view (or the DefiningQuery in the EDMX) to contain a column based on the ROW_NUMBER function so you can use it as the primary key.

Related

Entity Framework Core - Cannot remove optional nullable relationship

Using Microsoft.EntityFrameworkCore version 5.0.7 and Npgsql.EntityFrameworkCore.PostgreSQL version 5.0.7, I'm currently stuck trying to remove a relationship and have that change stored. Assume two models:
public class Banana {
public int Id { get; set; }
public Consumer? Consumer { get; set; }
}
public class Consumer {
public int Id { get; set; }
}
I'm able to assign a consumer just fine using
myBanana.Consumer = dbContext.Consumers.First(row => row.Id == 1);
dbContext.Update(myBanana);
dbContext.SaveChanges();
and that works just fine - the database is updated accordingly. However, once that is stored, trying to remove the reference again using
myBanana.Consumer = null;
dbContext.Update(myBanana);
dbContext.SaveChanges();
fails. After saving, the old value is still in the database, not null as I would expect. Interestingly, other changes to the banana are saved just fine.
I'm not sure if I'm hitting a weird issue with Nullables, or if I'm just missing something, so I'd appreciate some hints.
If you want to continue using auto-generated foreign key properties, you have to make sure that the navigations are loaded. If they are lazy-loaded by default and you don't manually load them, the property will already be null before you try to assign null, so EF can't observe a change.
Loading the navigation with .Include(banana => banana.Consumer) works, and so does loading it via dbContext.Entry(myBanana).Reference(banana => banana.Consumer).Load(). After the relevant navigation items are loaded, myBanana.Consumer = null from the example in the question works as expected.
If you have a non-tracking entity to work with (for example because it was generated by Model Binding), you can either get a tracking entity, or change the value of the auto-generated foreign key shadow property directly:
dbContext.Entry(myBanana).Property("ConsumerId").CurrentValue = null;
which also works. This may be a little bit less polished since you depend on a string as the field name to be correct, but it can be a valid option depending on the circumstances.

Saving OData entities from api with id to database in entity framework

I have multiple projects that return the same OData entities through a API endpoint. Now i want to call all of the projects and store them in my calling projects database with entity framework.
To add them to the db the ID gets overwritten but i want to save the id that the entity has in the projects database as well. so i can still access them if need be and to check if the data isn't already in my database. Because of this i need to add another MainProjectID and projectID column to the entity.
I tried making a new class that has a reference to the entity i want to save but this used new id's for the entities. I also tried inheriting the class but this gave me key conflict issues, and generics don't work either in entity framework(i'm not saying they should). So i'm kinda at a loss right now.
I basically want to save the id as a non-key. Is there any way i can do this without writing entirely new classes and parsing them manually ?
Any help would be greatly appreciated.
We have multiple alternatives here:
In a distributed system, best way to cope with these kinds of ID clashes is to make IDs globally unique. If you can modify how IDs are generated, that would be my choice to go. You can use a UUID (or Microsoft implementation GUID) that will produce a universal unique identifier. Or if that seems like an overkill you can devise a simple mechanism that combines ID with projectID. However you should ensure that the method you will use will not produce any collisions (no two different id-projectId pair will map to same value).
This will ensure that same entity is used throughout your application and no overlaps occur if you try to put records from different sources into the same table. You only need to implement a mechanism to record which ID originated from which source. You can use a reference entity at aggregator for this purpose. You also need to disable auto increment nature of the ID column so that your global unique values are used in table.
You can use different entities for producing and aggregating applications. I don't know your application, but that seems like an OK approach to me since the aggregating application has a different idea about the entity. The aggregating application cares for which application produced the entity, that might make putting the source application identifier into the entry justifiable. Your entities will only differ in that and when you receive the OData object from API you'll need copy all other properties and put project identifier yourself.
You can use the previous solution, but you can use a derived class in order to not to repeat your object properties. This is a better design alternative. However with this method you'll have some problems with the primary key (as you've stated you had). Consider this example
public class Base {
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Name")]
public string Name { get; set; }
}
public class Derived : Base {
[Key]
public int projectId {get; set; }
}
If you don't put [Key] to Derived then you'll have only ID as primary key. When you put [Key] to Derived then you'll have only projectId as primary key. You need to define a composite key and you can do this by removing the [Key] annotation from projectId and using the onModelCreating override of DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Derived>().HasKey(a => new { a.ID, a.projectId })
.Property(c => c.ID).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
modelBuilder.Entity<Derived>().Property(c => c.projectId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
}
You can mix these alternatives. You can drop the primary key on ID field, and then you can insert a new Entity that will model 1-M relationship between ID's and project ID's.

How to implement GetHashCode for NHibernate entity with identity column?

I came to a conclusion that it is impossible to properly implement GetHashCode() for an NHibernate entity with an identity column. The only working solution I found is to return a constant. See below for explanation.
This, obviously, is terrible: all dictionary searches effectively become linear. Am I wrong? Is there a workaround I missed?
Explanation
Let's suppose we have an Order entity that refers to one or more Product entities like this:
class Product
{
public virtual int Id { get; set; } // auto; assigned by the database upon insertion
public virtual string Name { get; set; }
public virtual Order Order { get; set; } // foreign key into the Orders table
}
"Id" is what is called an IDENTITY column in SQL Server terms: an integer key that is automatically generated by the database when the record is inserted.
Now, what options do I have for implementing Product.GetHashCode()? I can base it on
Id value
Name value
Identity of the product object (default behavior)
Each of these ideas does not work. If I base my hash code on Id, it will change when the object is inserted into a database. The following was experimentally shown to break, at least in the presence of NHibernate.SetForNet4:
/* add product to order */
var product = new Product { Name = "Sushi" }; // Id is zero
order.Products.Add(product); // GetHashCode() is calculated based on Id of zero
session.SaveOrUpdate(order);
// product.Id is now changed to an automatically generated value from DB
// product.GetHashCode() value changes accordingly
// order.Products collection does not like it; it assumes GetHashCode() does not change
bool isAdded = order.Products.Contains(product);
// isAdded is false;
// the collection is looking up the product by its new hash code and not finding it
Basing GetHashCode() on the object identity (i.e. leaving Product with default implementation) does not work well either, it was covered on StackOverflow before. Basing GetHashCode() on Name is obviously not a good idea if Name is mutable.
So, what is left? The only thing that worked for me was
class Product
{
...
override public GetHashCode() { return 42; }
}
Thanks for reading through this long quesiton.
Do you have any ideas on how to make it better?
PS. Please keep in mind that this is an NHibernate question, not collections question. The collection type and the order of operations are not arbitrary. They are tied to the way NHibernate works. For instance, I cannot simply make Order.Products to be something like IList. It will have important implications such as requiring an index/order column, etc.
I would base the hashcode (and equality, obviously) on the Id, that's the right thing to do. Your problem stems from the fact that you modify Id while the object is in the Dictionary. Objects should be immutable in terms of hashcode and equality while they are inside a dictionary or hashset.
You have two options -
Don't populate dictionaries or hashsets before storing items in DB
Before saving an object to the DB, remove it from the dictionaries. Save it to the DB and then add it again to the dictionary.
Update
The problem can also be solved by using others mappings
You can use a bag mapping - it will be mapped to an IList and should work OK with you. No need to use HashSets or Dictionaries.
If the DB schema is under your control, you may wish to consider adding an index column and making the relation ordered. This will again be mapped to an IList but will have a List mapping.
There are differences in performance, depending on your mappings and scenarios (see http://nhibernate.info/doc/nh/en/#performance-collections-mostefficientupdate)

EF (code-first): How should I change the value of a 1:1 association?

I have a File entity and a User entity. The File entity has a 1:1 relationship with the User entity through a property called LastChangeUser (this records the user who last changed the file). There's also a field within the File entity named LastChangeUserId, which is the actual FK relationship. The relationship is one-way: the User entity has no navigation property leading back to the File entity.
class File
{
public int Id { get; set; }
public int? LastChangeUserId { get; set; }
public virtual User LastChangeUser { get; set; }
}
class User
{
public int Id { get; set; }
}
When a File is changed, I need to set the LastChangeUser for the File. I only have the ID of the user to hand, not the complete User object. So, I'm doing this:
file.LastChangeUser = null;
file.LastChangeUserId = userId;
This seems to work on creating the file, when the File object is newly-created (a POCO which is then added to the entity collection).
However, it does not work when updating the file, when the File object is an existing object retrieved (as a proxy) from the DB.
In the latter case, I end up with a NULL in the DB for the LastChangeUserId field. (After a call to SaveChanges, the object has null in both the LastChangeUser and LastChangeUserId fields).
Maybe I'm just doing the wrong thing here? What's the right way? Do I really need to go get the User object in order to set the LastChangeUser property?
The reason #kevin_fitz solution works here is due to the way changetracking and validation in EF work. The default behavior for change tracking in EF is a method called snapshot tracking which essentially clones an initial state of every entity when its first loaded. When you go to save changes in EF the original snapshot for each entity is compared to the current state object (the one which you are modifying) and any differences are persisted to the database.
Along side this EF also performs pre-submit validation on entities (which can FYI be disabled).
In your case you have made two changes to the model which will be detected by the snapshot tracker on save (and they actually conflict). The tracker however will try and process both of these through the validation rules which will pickup that this is a required relationship and cant be set to null. This is why you are seeing this error and why removing the null update fixes your problem.
On a sidenote, you actually only need to update either the object or the key on a navigation property to trigger that database relationship to be updated. For more details on how navigation properties work in EF codefirst checkout my article here: http://blog.staticvoid.co.nz/2012/07/entity-framework-navigation-property.html

EF4 exception with relationship

I have two entities and there are their POCO:
public class DocumentColumn
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual long? DocumentTypeId { get; set; }
}
public class DocumentType {
public virtual long Id { get; set; }
public virtual string Name { get; set; }
}
There is a relation between those two entities. In the db the relation called:FK_T_DOCUMENT_COLUMN_T_DOCUMENT_TYPE.
When I do:
DocumentColumns.Where(x => x.DocumentTypeId == documentTypeId).ToList();
I get the exception:
{"Metadata information for the relationship 'MyModel.FK_T_DOCUMENT_COLUMN_T_DOCUMENT_TYPE' could not be retrieved. If mapping attributes are used, make sure that the EdmRelationshipAttribute for the relationship has been defined in the assembly. When using convention-based mapping, metadata information for relationships between detached entities cannot be determined.\r\nParameter name: relationshipName"}
I tryed to remove the relationship and the DocumentColumn table and reload them but the code still throws the exception.
Whet does this exception means and how can I solve it?
EDIT:
The exception happens also If I do DocumentColumns.ToList();
(Presuming you are talking about Code First ....)
There is no information in either class to let CF know that there is a relationship between them. It doesn't matter that the database has the info. Entity Framework needs to have a clue about the relationship. You provide only a property with an integer. CF cannot infer a relationship. You must have something in one class or another that provides type or another. This is not a database. It's a data model. Very different things.
But that's not all. I'm guessing that this is a one to many relationship. You could either put a List property into the Document class or a Document property in the DocumentColumn class. If you only do the latter, CF and EF will NOT know about the 1:. It will presume a 1:1 (that is if you leave DocumentId integer in there, otherwise it will presume a 1:0..1). However, I think you could get away with this and then just configure the multiplicity (1:) in fluent API.
UPDATE...reading your question again, I think you are using an EDMX and designer not code first. What are you using to create your POCO classes? Are you doing code gen from the EDMX or just writing the classes. I still think the lack of a navigation property in at least ONE of the types might be the cause of the problem. The ERROR message does not suggest that...I'm only coming to this conclusion by looking at the classes and inferring my understanding of how EF works with the metadata. I could be barking up the wrong tree. FWIW, I have asked the team if they are familiar with this exception and can provide some idea of what pattern would create it. It's pretty bizarre. :)
It seems odd to me that you are using EF with a defined relationship and you are not using the related property. Can you not do:
DocumentColumns.Where(x=>x.DocumentType.Id == documentTypeId).ToList();
This is what I would expect to see in this instance.

Categories

Resources