One of the biggest challenges with Entity Framework for updates is that if you want to move objects across tiers, you lose the object’s state. All of the state and change tracking information is maintained by the object context. While entity objects are serializable (xml and binary) and can be transported across tiers, object context is not.
Therefore, if you have objects being managed by an ObjectContext in one tier, then move them to another tier and attach them to a new ObjectContext, you will have the current data, but nothing else and the object will be seen as “unchanged”.
If you want to use the ObjectContext’s SaveChanges method to do your db updates (I am using the term “update” generically to refer to updates, inserts and deletes, aka “the CUD in CRUD”), nothing will get sent to the server since it’s all seen as unchanged.
There are a few ways we have seen to get around this., but they have all boiled down to this:
Solution #1 Ignore concurrency and perform an inefficient update
Buried in replies by team members in the ADO.NET Orcas forums there is a conceptual solution that leverages the EntityState object (combined with EF’s MetaDataWorkSpace or reflection) to mark the object as modified and then iterate through ALL of the properties in your object and change their state to “modified”. Now SaveChanges will push all of the current data up to the server.
What I like about this solution:
It’s reusable code that will work with any entity which you throw at it.
What I don’t like about this solution:
1) What is hugely unsatisfying to me is that this is a pure “last data in wins” scenario. While this is probably all that many solutions require, it means that there is no interest in handling any possible conflicts for data that may have changed on the server in between the user’s fetch and subsequent save.
2) Every property is flagged for update. Every property value is sent to SQL Server and every property value is updated in the database. Icch.
Solution #2 Write object update code for every single entity class
Another workaround I came up with was to extend the entity class so that it knows how to update itself. This assumes we have kept the original data hanging around somewhere. For each class, I created a method that takes in the current entity and the original entity, then I have some code that explicitly updates fields that I care about updating. Here’s part of that, where I’m dealing with updates, but not deletes and inserts.
This works – the original order now has the current values updated to the new values and the state is impacted correctly for the properties and the entity itself. Then I can use SaveChanges or other functionality that I’ve exposed in my model such as stored procedures to get the data to the server. The key here is that I can leverage the framework for my DML.
But this solution stinks because I have to write the same method over and over again for every single class.
Solution that I want and finally figured out: Update only relevant properties and have the option to deal with concurrency on a granular level
Mike Taulty actually pulled this off in this blog post, however I explicitly chose to hold off on reading Mike’s post. 🙂 I didn’t want to be influenced by his methods. What’s interesting is that in the end, we came up with very similar solutions, even though I went down many many paths before I found success! (I’m sure it took me about 10 times as long as it took Mike though.) Some evidence of my doggedness is in Dave Burke’s post about what an un-fun girl I was on Saturday night at code camp.
Updating for efficiency and concurrency
So here’s what I came up with as part of a larger solution of using entity framework in an ASP.NET website, surviving post backs, keeping the original values around and being able to use SaveChanges and also for using entities across tiers (eg web services).
I use reflection and the Entity Framework MetaDataWorkspace to accomplish this.
I’ll write another post about how I’m keeping the original values and surviving the postbacks on the ASP.NET site.
#1 important rule that has been discovered by everyone who is trying to solve this problem in Entity Framework and in LINQ to SQL, use a datetime field in your entity. This can be a field that is already in your database table or a special property exposed only by the conceptual model (or an extension to the entity’s class). If it’s in the database, you’re original data is fine, but you need to be sure to update that field any time you make mods to the properties of the entities. If you create a special property in the EDM, then you’ll also need to set the date when you first retrieve data from the data store.
Below, I am starting with a collection of original objects and a collection of current objects in hand. The collections are generic lists as in List<CustomerEntity>.
Step 1: Get a list of only those objects that have been altered since the data was first retrieved as well as objects to be inserted and objects to be deleted.
LINQ to the rescue!
In my example, origCustList is the List(Of Customer) with the original objects. custList is the List(Of Customer) with the new objects.
Inserts and deletes are not so complicated.
For inserts, I only need to query the current customers for those with no ID.
Dim inserts = custlist.Where(Function(c) (c.CustomerID=0))
Now I only need to add the new customers to my ObjectContext.
For Each insCust In inserts
I cheated by using AddToCustomer for the inserts. For a more generic solution, just use the AddObject method instead.
For deletes I query the original customers and find any that don’t have a match in the new customers.
Dim deletes = From oc In origCustList _
Where Not custlist.Any(Function(c) (c.CustomerID=oc.CustomerID))
Next you need to attach, then delete these objects from the ObjectContext. These steps for delete may not seem intuitive. The object needs to be known by the ObjectContext (therefore you attach it) and then deleting it from the ObjectContext marks it as a record to be deleted when we SaveChanges back to the database.
For Each delCust In deletes
Now come the updated cystomers. Here’s where it gets fun!
I’m using LINQ to join on customerid and grab objects that have a newer dateModified value. This is where the date property pays off. Otherwise, I’d have to check every property of every object to see if anything changed.
Dim updates = From oc In origCustList Join cc In custlist _
On oc.CustomerID Equals cc.CustomerID _
Where oc.ModifiedDate <> cc.ModifiedDate _
Select oc, cc
Now I have a pair of related objects for each modified customer.
In order to emulate a scenario where the objects have been tracked by the ObjectContext from the start, I need to attach the original objects to this new ObjectContext and then make any necessary changes to the objects. I iterate through the collection of object pairs and pass them off to a custom method, updateEntityProperty.
For Each c In updates
updateEntityProperties(aw, c.oc, c.cc)
Here is where the cool stuff happens!
I want to preface this by saying that I tried using the ObjectStateEntry to pull this off, but it didn’t give me quite enough capabilities as I needed for this solution. The class allows you to change the state of the entity to Modified and to change the state of the individual properties to modified, but I also needed to change the values of the properties, which I couldn’t acheive with the state manager (which makes perfect sense – values are not state). [Update – apparently what I needed was in there; I just couldn’t find it and after trying for some time, chose to move on to reflection.] So instead I am using reflection to help me modify the values programmatically. When I do this, the state changes automatically.
Although I didn’t get to use ObjectStateEntry, I am leveraging the MetaDataWorkspace which is also part of the Entity Framework.
Private Sub updateEntityProperties(ByRef context As AdventureWorksModel.AWModel.Entities, ByRef origcust As Customer, ByRef newcust As Customer)
Dim mdw As Data.Metadata.Edm.MetadataWorkspace = context.MetadataWorkspace
‘do an explicit declaration of Edm.EntityType, otherwise custtype becomes an edmtype
‘which doesn’t expose properties that I want to grab through reflection!
Dim custtype As Edm.EntityType = _
mdw.GetType(GetType(Customer).Name, “AWModel”, Edm.DataSpace.CSpace)
For Each p In newcust.GetType.GetProperties ‘<- that’s Reflection at work
Dim newprop = p.GetValue(newcust, Nothing)
If newprop Is Nothing Then
If p.GetValue(origcust, Nothing) IsNot Nothing Then
p.SetValue(origcust, newprop, Nothing)
If Not newprop.Equals(p.GetValue(origcust, Nothing)) Then
‘if we are here, then the value in new cust is different from the original
‘SKIP THIS FOR NOW IF IT’S A PROPERTY COLLECTION –
‘TO DO: build in recursive behavior to update property collections as well
‘simplest test to see if it’s a property collection or not: straight values are a System
If newprop.GetType.Namespace = “System” Then
p.SetValue(origcust, newprop, Nothing)
So, this is not quite perfect yet, but it does the job and I’m really happy that I got the concept worked out. I need to do a few more things to make this work generically and I also need to add in recursive mods to this. For example, the Customer has a property collection of addresses. In the above code, I am ignoring them, but I need to make this solution so that if it hits a property collection (this is where I’m testing for a System type – if it’s not system type then it’s a property collection that is exposing child records) that it calls the same UpdateEntityProperties on that entity as well. SaveChanges will take care of all of the related records that are in the ObjectContext.
While this isn’t a cut & paste solution like Mike’s, i hope that the explanations help you to understand how and why this works. There are quite a lot of lessons that I learned during this exercise.
I have two more patterns that I have worked out which I want to write about. The first is dealing with the state when working in an ASP.NET application (postbacks trash the objectcontext, too) and the other is doing xml serialization on the entire object graph. Danny Simmons gave me a hint for how to approach the graph problem, and I was able to work it out from there.
Sign up for my newsletter so you don't miss my conference & Pluralsight course announcements!