Rolling your own SQL Update on top of the Entity Framework - Part 2

Okay so it has taken me a while to get to the second part of this post... but as they say better late than never.

As I've said before when designing this sort of API, I always like to start with the end in mind... this is what I want the Update() method to look like:

public static int Update<T>(this IQueryable<T> queryable,
Expression<Func<T, T>> updator) where T : EntityObject
{
    SqlDmlCommandFactory<T> factory = new SqlDmlCommandFactory<T>(queryable);
    SqlCommand updateCmd = factory.GetUpdateCommand(updator);
    return updateCmd.ExecuteNonQuery();
}

So lets take a look at the constructor of SqlDmlCommandFactory:

public SqlDmlCommandFactory(IQueryable<T> queryable)
{
    _query = queryable as ObjectQuery<T>;
    Utilities.AssertNotNull(_query, () => new NullReferenceException("You can only construct a SqlDmlCommandFactory from an ObjectQuery<T>"));
    _context = new SqlDmlContext<T>(queryable);
}

The first step is make sure we are extending an ObjectQuery<T>. Then I create a SqlDmlContext<T>. This simply about separation of concerns. Why? Well the work an ObjectQuery<T> can be used as the foundation for both an Update and a Delete (and perhaps even a Bulk insert, but more about that in the future ;-), so it makes sense to pull some of the core logic out.

public SqlDmlContext(IQueryable<T> queryable)
{
    ObjectQuery<T> query = queryable as ObjectQuery<T>;
    Utilities.AssertNotNull(query, () => new NotSupportedException("A metacontext only works against ObjectQuery<T> not the general IQueryable<T> case"));
    SetObjectContext(query.Context);
}

SetObjectContext(..) looks like this:

private void SetObjectContext(ObjectContext context)
{
    Utilities.AssertNotNull(context, () => new NullReferenceException("Need an ObjectContext to create MetaContext<T>"));
    _context = context;
    _workspace = context.MetadataWorkspace;
    _entityType = _workspace.GetItem<EntityType>(typeof(T).FullName, DataSpace.CSpace);
    Utilities.AssertNotNull(_entityType, () => new NotSupportedException("The ObjectContext provided doesn't support type: " + typeof(T).FullName));
    _keys = _entityType.KeyMembers.Select(m => m.Name).ToArray();
}

So you can see SqlDmlContext<T> then is simply responsible for holding onto the ObjectContext, the metadata, the EntityType corresponding to T and the names of its keys, and making these things accessible via public properties (not shown in the interest of brevity).

Next we need to look at GetUpdateCommand(..)

There are two key parts to this...