Self-Tracking Entities: ApplyChanges and duplicate entities
Some customers using the Self-Tracking Entities template we included in Visual Studio 2010 have ran into scenarios in which they call ApplyChanges passing a graph of entities that they put together in the client tier of their app, and then they get an exception with the following message:
AcceptChanges cannot continue because the object’s key values conflict with another object in the ObjectStateManager.
This seems to be the most common unexpected issue our customers run against when using Self-Tracking Entities. I have responded multiple times to this on email and I have meant to blog about it for some time. Somehow I was able to do it today :)
We believe that most people finding this exception are either calling ApplyChanges to the same context with multiple unrelated graphs or they are merging graphs obtained in multiple requests so that they now have duplicate entities in the graph they pass to ApplyChanges.
By duplicate entities, what I mean is that they have more than one instance of the same entity, in other words, two or more objects with the same entity key values.
The current version of Self-Tracking Entities was specifically designed to not handle duplications. In fact, when we were designing this, our understanding was that a situation like this was most likely the result of a programming error and therefore it was more helpful to our customers to throw an exception.
The problem with the exception is that avoiding introducing duplicate entities can be hard. As an example, let’s say that you have service that exposes three service operations for a car catalog:
- GetModelsWithMakes: returns a list of car Models with their respective associated Makes
- GetMakes: returns the full list of car Makes
- UpdateModels: takes a list of car Models and uses ApplyChanges and SaveChanges to save changes in the database
And the typical operation of the application goes likes this:
- Your client application invokes GetModelsWithMakes and uses it to populate a grid in the UI.
- Then, the app invokes GetMakes and uses the results to populate items in a drop down field in the grid.
- When a Make “A” is selected for a car Model, there is some piece of code that assigns the instance of Make “A” to the Model.Make navigation property.
- When changes are saved, the UpdateModels operation is called on the server with the graph resulting from the steps above.
This is going to be a problem if there was another Model in the list that was already associated with the same Make “A”: since you brought some Makes with the graph of Models and some Makes from a separate call, you now have two completely different instances of “A” in the graph. The call to ApplyChanges will fail on the server with the exception describing a key conflict.
There are changes we have considered doing in the future to the code in ApplyChanges in order to avoid the exception but in the general case there might be inconsistencies between the states of the two Make “A” instances, and they can be associated with a different Models, making it very difficult for ApplyChanges to decide how to proceed.
In general, the best way to handle duplicates in the object graph seems to be to avoid introducing them in the first place!
Here are a few patterns that you can use to avoid them:
1. Only use Foreign Key values to manipulate associations:
You can use foreign key properties to set associations between objects without really connecting the two graphs. Every time you would do something like this:
model.Make = make;
… replace it with this:
model.MakeId = make.Id;
This is the simplest solution I can think of and should work well unless you have many-to-many associations or other “independent associations” in your graph, which don’t expose foreign key properties in the entities.
2. Use a “graph container” object and have a single “Get” service operation for each “Update” operation:
If we combine the operations used to obtain car Models and Makes into a single service operation, we can use Entity Framework to perform “identity resolution” on the entities obtained, so that we get a single instance for each make and model from the beginning.
This is a simplified version of “GetCarsCatalog” that brings together the data of both Models and Makes.
// type shared between client and server
public class CarsCatalog
{
public Model[] Models {get; set;}
public Make[] Makes {get; set;}
}// server side code
public CarsCatalog GetCarsCatalog()
{
using (var db = new AutoEntities())
{
return new CarsCatalog
{
Models = context.Models.ToArray(),
Makes = context.Makes.ToArray()
};
}
}// client side code
var catalog = service.GetCarsCatalog();
var model = catalog.Models.First();
var make = catalog.Makes.First();
model.Make = make;
This approach should work well even if you have associations without FKs. If you have many-to-many associations, it will be necessary to use the Include method in some queries, so that the data about the association itself is loaded from the database.
3. Perform identity resolution on the client:
If the simple solutions above don’t work for you, you can still make sure you don’t add duplicate objects in your graph while on the client.The basic idea for this approach is that every time you are going to assign a Make to a Model, you pass the Make through a process that will help you find whether there is already another instance that represents the same Make in the graph of the Model, so that you can avoid the duplication.
This is a really complicated way of doing it compared with the two solutions above, but using Jeff’s graph iterator template, it doesn’t really take a lot of extra code to do it:
// returns an instance from the graph with the same key or the original entity
public static class Extensions
{
public static TEntity MergeWith<TEntity, TGraph>(this TEntity entity, TGraph graph,
Func<TEntity, TEntity, bool> keyComparer)
where TEntity : class, IObjectWithChangeTracker
where TGraph: class, IObjectWithChangeTracker
{
return AutoEntitiesIterator.Create(graph).OfType<TEntity>()
.SingleOrDefault(e => keyComparer(entity,e)) ?? entity;
}
}// usage
model.Make = make.MergeWith(model, (j1, j2) => j1.Id == j2.Id);
Notice that the last argument of the MergeWith method is a delegate that is used to compare key values on instances of the TEntity type. When using EF, you can normally take for granted that EF will know what properties are the keys and that identity resolution will just happen automatically, but since on the client-side you only have a graph of Self-Tracking Entities, you need to provide this additional information.
Summary
Some customers using Self-Tracking Entities are running into exceptions in cases in which duplicate entities are introduced, typically when they have entities retrieved in multiple service operations merged into a single graph. ApplyChanges wasn’t designed to handle duplicates and the best practice is to avoid introducing the duplicates in the first place. I have showed a few patterns that can help with that.
Personally, I believe the best compromise between simplicity and flexibility is provided by a combination of the first and second patterns. For instance, you can use only foreign keys properties to associate entities with reference/read-only data (e.g. associate an OrderLine with a Product in an application used to process Orders), and use graph containers to transfer data that can be modified in the same transaction, i.e. entities that belong in the same aggregate (e.g, Order and its associated OrderLines).
Hope this helps,
Diego
Comments
Anonymous
October 09, 2010
Hi Diego, I´d like to use DDD aproach on ASP.NET WebForms App, so I have my Poco Classes in separate assembli of the .edmx Model, I´m serializing the My POCO classes in ViewState and it works fine to Insert and Retrive data from my Repository, also the LazyLoading with the Virtual in my List<T> properties works fine. But, to persist in Data Base the updated object graph is another long story. I´ve been looking for one easy answer to that but I did´t find yet, belive me I´ve been reading a lot about EF. This Aproach with STE looks to be more apropriate to use with WCF stuff I´m not using services on my App. I was trying to use the STE generator but my first problem was It´s generated in the same assemblie of .edmx Model and if I move that to my Domain assemblie to try to use partial Classes in My domain I lost the Interfaces IChangeble...... ITracklebel.... Can you give me any tip just to Save back my object Graph with the modifications, the Funny part is The modifications are there in the object But the modifications (add or delete ) of my lists are ignored by the EF4. Thanks, EdmilsonAnonymous
November 05, 2010
Thank's Diego for writing this. When is the new version of STE, which solves this issue, going to be available??Anonymous
November 12, 2010
Hi Diego, I am running into the problem described in the post and was hoping to come up with a pure server side solution because it is very difficult to control the all the use cases on the client. The solution I had at first was to override Equals and GetHashCode to compare on entity keys, and while it worked great when one entity was persisted at any given time, the solution broke down when ApplyChanges was called on multiple entities sequentially. So, is there a server side solution that you would recommend? And is there a fix on the horizon that we can expect? Thanks, Alex. abesidski@hotmail.comAnonymous
November 30, 2010
Very nice article. This made things a lot clearer. But I have still issues with many-to-many relations. Option 1 will work in most scenarios, but as you said, not in many-to-many relations. Option 2 will work, but will work, but then we need to fetch a lot of data we do not need, to add just one object. Option 3, we would rather not use :-) Is it any other options when working with many-to-many relations? Regards Magnus RekkedalAnonymous
December 08, 2010
Hello, I'm facing this problem and never received any response to my numerous posts on the web. I do not understand why having 2 entities with the same EntityKey is a problem if those entities are in an unchanged state. STE is unusable in a real world app. I've a lot of many-to-many tables and thus, cannot bind to ID as stated in your first point. The second one is a performance-killer, i do not want to download a complete list of Make with each Model, just to be able to select one. Concerning the third point, it's not an example for a many-to-many table. Waht about a merge for TrackableCollection<T>? I'm completely stuck on a project using Silverlight / WCF and EF 4 STE. Can you please tell me how to proceed to save the entity back to the database? Thanks in advance DavidAnonymous
December 22, 2010
The comment has been removedAnonymous
February 02, 2011
Thanks, this article provides some good insights. We use kind of the same technique as in work-around 3. Except we do it on the server-side, when a graph arrives at the server, right before calling, AcceptChanges. At this point, we search for duplicates and throw duplicates out. This only works when the duplicates don't have changes them selves. But this works out fine in our scenarios, where like in this example, the duplicates usually come from a selection to link them to a Parent entity. This took us quite some time to figure out though. Some guidelines on what (and especially what not) is possibly with STE's would be nice. Without such documentation, we keep running into "unexpected" issues like this, which take a lot of time to figure out. Still struggling with other issues, like re-linking detached entities. (see social.msdn.microsoft.com/.../4fadd41f-3157-43cb-b5e1-7def59aacdb5) We feel like STE's were/are not mature enough to go live with. Thanks for your time, KoenAnonymous
February 07, 2011
Hi, I have found an issue with the Iterator solution to this problem. In almost all cases it works but in this one case its still an issue and the iterator solution is the only solution we can use. Lets say I have two Person properties and a find person screen. I find a person and set it to person 1 using the merge. I find a person and set it to person 2 using the merge and save. This works correctly. I go back into it and i find the same person set on person 2 and set it to person 1, I find the same person that was previously in person 1 and set it to person 2. What this causes is two copies of the person that was originally in person 1. The reason for this is when i set person 1 to person 2 it properly finds the merge and uses it, but then what was in person 1 originally is now in OriginalValues. When i set person 2 to what was originally in person 1 the iterator does not find it because it exists in OriginalValues as an old value. When apply changes is called it still hits this issue because two copies exist of the same object, one directly in a navigation property and another in original values. Is there any way to fix this? This issue overall is causing some major headaches. I dont see why it is so hard to have the concept of finding a record and bringing it into an object graph to save the key. We have to have the actual objects for modification purposes and display purposes. there are other ways we can solve this in the UI layer but we really want to solve this in the model layer. ThanksAnonymous
April 19, 2011
This is a great fault by Microsoft in something that must be an easy task. Java allows works without problem, doing that i'm trying to do with STE... without the need to do any workaround.Anonymous
January 21, 2012
I ran into this problem a lot and found that it could also be resolved by overriding the Equals method of each entity class to compare the key values. Reading the above, are you saying that this should not have worked?Anonymous
February 21, 2012
Hi @Martin, Could you show how you override the Equals method? Thank you.Anonymous
April 26, 2012
Just an additional note on only using the foreign key ID: I needed to be able to attach the same Product to PurchaseDetailLines and was getting the error, but my grid view uses the Product.Name property. Since everthing related to products was read-only I was able to do a foreach loop through the detail lines and set the Product entity equal to null. The foreign key kept and I was still able to use the referenced objects before the save.Anonymous
November 27, 2013
Hi guys This is very interesting... However, I'm getting that exception too but in a different scenario I guess.. I have already a recond In DB/DataContext. I deleted the record from DB and detach it from EntityContext. Then a second user comes, executes same logic, detects that the record no longer exist but needs it, so it is created again.. All this happens in two separated threads and both start at the same time (A user clicking OK to same Form for example). When I accept all changes for the record that is being created I get that Exception. Seems like the one I already deleted somehow still remains in memory/cache.. so, when the second thread tries to created again that record and acept the changes it conflicts with the other one.