共用方式為


Building N-Tier applications with the EF – The Basics

Today I was looking at a post in the forums where someone asked a very natural and common question about the EF that I end up answering pretty frequently.  So I decided to put my answer here on my blog to make it easier to refer back to the answer in the future.  The question was essentially this:

I’m building a silverlight client (but it could just as easily be a rich client or an ajax app or any other client of a WCF service) with a WCF service on the mid-tier that uses the EF to persist changes to a database.  I can easily write a WCF service method to retrieve entities, and I can also create one which will add new entities to the database by just calling AddObject and then SaveChanges, but I can’t figure out how to do an update.  I was hoping there would just be an UpdateObject method or something like that.  What do I do?

Unfortunately this is more difficult than we'd like it to be in EF3.5 (the first release).  It's a bit difficult because the problem space is more complicated under the covers than it seems to be on the surface, so you have to think carefully about a few things.  That said, the EF API could help more in these scenarios, so in EF4 we're working on a series of improvements which you can read about in posts on the EF Design blog such as this one: blogs.msdn.com/efdesign/archive/2009/03/24/self-tracking-entities-in-the-entity-framework.aspx
In the meantime, there are three important things you need to think about:

  1. Keeping track of concurrency tokens for the purpose of doing concurrency checks when you update (you don't want to blindly overwrite a change that some other client has made to the same part of the database between the time when you read the data and the time when you try to write back your changes).
  2. Keeping track of which properties you have modified on an entity.  This is important if you want to make sure that the update to the database only includes the modified properties.  If you are OK with sending an update statement that includes even unmodified properties (which by the way can sometimes even be faster because it might allow the database to reuse a query plan rather than generating a new one for each combination of modified props), then it may be OK not to worry about this one.
  3. If you have a graph of related entities then you need to worry about tracking what kinds of operations happened to what parts of the graph--one entity modified, another one is a newly added entity, another one deleted, etc.

All of this amounts to tracking this information on the client side, communicating it back to your mid-tier through your WCF service methods, and then replaying information to the ObjectContext so the changes can be persisted.  If you skip option #3 for now (one of the hardest parts with EF3.5 and something which can occasionally be skipped if you are willing to just have WCF service methods which operate on one entity or which treat all entities in the graph the same way--so you have separate methods for add, update and delete), then you already know how to do the add method.  The delete method is also pretty straight forward, you just need to call Attach on the entity you pass to the method to bring it into the context and then call DeleteObject on it and SaveChanges.  The interesting method, as you mention, is update...

The simplest but not terribly efficient way to solve the update method is to make a copy of the entity on the client before you make any changes so that at the time you want to update things you have both a copy of the original version of the entity and the modified one.  Then your update service method can look something like this:

 void Update(MyEntity original, MyEntity updated)
{
    using (var context = new MyEntities())
    {
        context.Attach(original);
        context.ApplyPropertyChanges(updated);
        context.SaveChanges();
    }
}

You can improve on this, though, if you are willing to make a few assumptions.  First, if you can guarantee that the client will not change the property used for concurrency checks, then you don’t need the original value of the concurrency property.  Secondly, if you are willing to always send all properties to the server on an update rather than just those properties which have changed, then you don’t need an original value for all the other properties to compare against to see if they are changed.  In this case you can get away with only sending a single, updated entity from the client back to the mid-tier, and you just have to use some trickier APIs to get the entity attached to the context in a form so that the EF will treat it as modified.  I wrote a previous blog post about how to do that.  With the extension method from that post, you can get rid of the original entity argument to the update method and just call the AttachAsModified method followed by SaveChanges().

- Danny

Comments