Tip 17 – How to do one step updates with AttachAsModified(..)

Background:

In Tip 13 - How to Attach the easy way I showed you how to ‘establish’ the EntitySet for a particular CLR Type so you could Attach it.

But in all the tips so far the same basic pattern is used:

  1. Attach the original version of the Entity
  2. Modify it
  3. SaveChanges

I’ve given you tips to make it easier to do (1) see Tip 13 and Tip 16. I’ve also covered the things you need to understand when doing (2) in Tip 7 and Tip 9 respectively.

But what about if you want to merge steps (1) and (2).

I.e. you already have the modified object, and you just want to attach it. Well this is where AttachAsModified(…) comes in.

Solution:

There are two key tasks that we want to handle in one step:

  1. Attach the entity (at this stage the EF thinks it is unchanged)
  2. Mark every property of the entity as modified.

To do this you could use the default entity set idea shown in Tip 13 and add an Extension method directly to ObjectContext.

But instead I’m going to build on the ideas of Tip 16, and add another extension method on ObjectQuery<T>, something like this:

public static void AttachAsModified<TEntity>(
this ObjectQuery<TEntity> query,
TEntity entity) where TEntity : EntityObject
{
if (query == null) throw new ArgumentNullException("query");
if (entity == null) throw new ArgumentNullException("entity");
// Uses method created in Tip 16
    query.Attach(entity);
    query.Context
.ObjectStateManager
.MarkAllPropertiesModified(entity);
}

The Attach(..) method is implement in Tip 16 so now all I need to add is an implementation of MarkAllPropertiesModified(…).

While it uses some obscure code from the bowels of the ObjectStateManager it is actually remarkably simple:

public static void MarkAllPropertiesModified<TEntity>(
this ObjectStateManager manager,
TEntity entity) where TEntity : EntityObject
{
if (manager== null)
throw new ArgumentNullException("manager");
if (entity == null)
throw new ArgumentNullException("entity");

// get the object state entry for the Entity
    var entry = manager.GetObjectStateEntry(entity);

// use metadata to get all the property names
// this is quicker and safer than reflection,
// because it ignores properties not in the model
var propNames =
from x in entry.CurrentValues.DataRecordInfo.FieldMetadata
select x.FieldType.Name;

// loop over every property and mark it modified
foreach (var propName in propNames)
entry.SetModifiedProperty(propName);
}

All it does is find the ObjectStateEntry for the attached entity and loop over all its properties and mark them as modified.

With these extension methods in place I can write code like this:

Customer updatedCustomer = GetUpdatedEntity();
ctx.Customers.AttachAsModified(updatedCustomer);
ctx.SaveChanges();

Very simple isn’t it?

Notice that for the first time I don’t have to phase my changes into before and after attach code. All the interesting changes to my entity are made before attaching it to the context.

This makes it much easier to create layers / tiers etc in your code.

Caveats:

As per usual References or the lack of Foreign Key properties in .NET 3.5 SP1 can cause some problems.

If you want to update the reference properties (i.e: customer.SalesPerson) you will need to re-introduce some before and after attach logic.

Before calling AttachAsModified(...) you can update all the Properties and but not the references. References will need to be their original values, because of the way the Entity Framework deals with Independent Associations. 

Then after calling AttachAsModified(…) you will need to update the references with the latest values, i.e. something like (customer.SalesPersonReference.EntityKey = … )

See Tip 7 for more on this.