About a month ago I wrote a blog post on an extension method for the Entity Framework called AttachAsModified, which was offered by Danny Simmons shortly after PDC as a way to perform disconnected updates from an n-tier service. Unfortunately, I discovered what I consider to be a bug in EF v.1 that limits your ability to use the AttachAsModified extension method. The problem surfaces when the entity you’re trying to update has reference properties to related entities. For example, say you have an Order entity with a Customer navigation property that relates it to the Customer entity. If Order contained a CustomerID property representing the foreign key, you could simply set its value. But the EF models this as an association between Order and Customer and does not include CustomerID as a foreign key in the Order entity. (The EF team, nevertheless, is considering allowing foreign keys into the model.)
The way to set reference properties in EF is to set the EntityKey of the reference property to a new key based on the foreign key value you want to set it to. For example, if you want to change the Customer that an Order is associated with, you need to do the following:
updatedOrder.CustomerReference.EntityKey = new EntityKey("NorthwindEntities.CustomerSet", "CustomerID", newCustomerID);
The problem is that, when you call SaveChanges on the ObjectContext, EF includes the non-reference Customer property in the WHERE clause of the UPDATE statement.
exec sp_executesql N'update [dbo].[Orders] set [CustomerID] = @0, [OrderDate] = @1, -- other fields set to null where (([CustomerID] is null and ([OrderID] = @2)) and ([RowVersion] = @3)) N'@0 nchar(5),@1 datetime,@2 int,@3 binary(8)', @0=N'ANATR',@1='1996-09-29 00:00:00:000',@2=10308,@3=0x000000000004BF3D
If you leave it null, or set it to anything other than the original value, this will result in an OptimisticConcurrencyException. This means you are required to pass in the Order’s original CustomerID value, which doesn’t make sense if you are using a timestamp column to manage concurrency (which is the purpose of the [RowVersion] in the above WHERE clause.
For this reason, I can’t bring myself to require the client app to retain original foreign key values, and I’m left having to re-query the database for the original entity, then set the timestamp property to that passed in with the updated entity. After detaching the entity to commit this change and mark it as ‘unmodified’, you can then modify the original entity properties to the values of the updated entity and call SaveChanges. While re-querying the database for the original entity isn’t pretty (and quite inefficient), I think it’s a better alternative to passing in the original values from the client (an approach advocated by MS architect Cesar de la Torre), because it won’t require code changes in the client when the EF team fixes the bug (hopefully before the release of v. 2!).
Here is a sample app that demonstrates the bug and also how to avoid it by re-querying the database. Cheers.