I just wrote an article for MSDN Magazine (due out in December) about developing n-tier applications for both LINQ to SQL and the Entity Framework. While researching the topic, I noticed a certain awkwardness with the Entity Framework API when it came to managing concurrency with a timestamp fields, as compared with LINQ to SQL. L2S has an overloaded Attach method that accepts a bool asModified parameter indicating the presence of a timestamp field used for concurrency. The Entity Framework, on the other hand, requires attaching the original unmodified entity, then calling ApplyAllChanges, passing in the modified entity. This doesn’t make a whole lot of sense when using a timestamp for concurrency checking, because the only original value you care about is the timestamp, which is passed in unchanged as part of the modified entity. Using timestamps is a great idea because it relieves the client from needing to cache the object’s original values and reduces the amount of data sent over the wire when you’re sending entities to a service for persistence. In the article, I got around this by querying the database from the service using the key value of the modified entity. Yuck!
Before you throw up your hands in despair, I have good news for you. Danny Simmons, a developer and program manager on the EF team, just came up with a couple extension methods to obviate the need for original entity values. Rather than taking the original entity and applying property changes from a detached object that has been modified, you can simply take the detached object and set the state of each property to modified. What you’re essentially saying is, “Look, I already have a modified entity, just change the state of each property from ‘Unchanged’ to ‘Modified’ so that when I call SaveChanges the updates are persisted.”
There’s one more wrinkle to this scenario: using Data Transfer Objects (DTOs) instead of EF entities in the Data Access Layer (DAL). Although v.2 of the Entity Framework will allow you to use DTOs directly as EF entities, this is not yet fully supported in v.1, meaning that an UpdateOrder method would accept a DTO.Order object, which you would use to create an L2E.Order object. To accomplish this, I created an extension method for ObjectContext called CreateEntityFromObject which accepts a DTO and uses reflection to copy properties from the DTO to the Entity and create an EntityKey. The code to persist changes to an Order entity now looks like this:
static DTO.Order UpdateOrder(DTO.Order order) { using (NorthwindEntities db = new NorthwindEntities()) { // Create new order entity from DTO.Order Order updatedOrder = db.CreateEntityFromObject <Order>("OrderSet", order); // Attach modified order (with original timestamp) db.AttachAsModified(updatedOrder); try { db.SaveChanges(); } catch (OptimisticConcurrencyException conflictEx) { Console.WriteLine(conflictEx.Message); return null; } return GetOrder(order.OrderID); } }
Here is the CreateEntityFromObject extension method:
public static TEntity CreateEntityFromObject<TEntity>
(this ObjectContext context, string entitySetName, object dto) where TEntity : IEntityWithKey, new() { // Create a new entity TEntity entity = new TEntity(); // Copy properties foreach (PropertyInfo dtoProp in dto.GetType().GetProperties()) { PropertyInfo entityProp = typeof(TEntity).GetProperty(dtoProp.Name); object propValue = dtoProp.GetValue(dto, null); entityProp.SetValue(entity, propValue, null); } // Set the entity key entity.EntityKey = context.CreateEntityKey(entitySetName, entity); // Return the entity return entity; }
To see all of this in action, you can download the code for my sample concurrency application. Enjoy!
I have just written a blog post that describes a bug in EF v.1, which has the effect of limiting the use of the AttachAsModified method such that you are required to supply the original foreign key values:
http://blog.tonysneed.com/?p=140
If you are using a timestamp for concurrency and do not wish to retain other original values, then your only alternative is to re-query the database for the original entity.