Background:

In order to be an EF power user today you really need to be familiar with EntitySets. For example you need to understand EntitySets in order to use AttachTo(…) or create EntityKeys.

In most cases there is only one possible EntitySet for each object / clr type. It is this idea that Tip 13 leverages to simplify Attaching objects and you can use similar tricks for Add too.

However to address this problem in .NET 4.0 we added a new class called ObjectSet<T>. ObjectSet<T> is used instead of ObjectQuery<T> for the return type of the property that represents an EntitySet property on the ObjectContext. It differs from ObjectQuery<T> because rather than just supporting queries it also allows you to Add and Attach entities.

So instead of writing this:

ctx.AddObject(“Customers”, newCustomer);

Where you need to specify the EntitySet name as a string (did I mention I hate strings?), ObjectSet<T> will allow you do this:

ctx.Customers.AddObject(newCustomer);

And unlike the solution presented in Tip 13ObjectSet<T> works even when there is more than one possible EntitySet for an object, aka MEST

ObjectSet<T> also has another very important benefit. It implements the IObjectSet<T> interface, so you can write your code and tests against the interface, which means it is easier to fake or mock your ObjectContext.

The next version of the EF is going to be pretty cool.

But can we mimic this feature in .NET 3.5 today?

Solution:

Well with extension methods it is actually incredibly easy to build a naive* solution:

We simply add an extension method to ObjectQuery<T> that makes it appear like it has a few other methods:

public static void AddObject<T>(
     this ObjectQuery<T> query,
T entity
)

public static void Attach<T>(
     this ObjectQuery<T> query, T entity
)

Once we’ve implemented these methods you can write the same sort of code you can in .NET 4.0:

ctx.Customers.Attach(oldCustomer);
ctx.Customers.AddObject(newCustomer);

Now in order to implement these methods we just need two things:

  1. The ObjectContext so we can actually do the adding and attaching.
  2. The name of the EntitySet associated with the ObjectQuery<T>

The ObjectContext is trivial. There is a property hanging off a ObjectQuery called Context which gives us what we need.

The name of the EntitySet is a little harder, the key to this is to use the CommandText property. Which is usually* just a string that looks something like this: “[EntitySetName]”,so all we have to do is get rid of the leading ‘[' and the trailing ‘]’ and we have the EntitySet name.

Since both methods need the EntitySet name lets make a method to get that:

public static string GetEntitySetName<T>(
    this ObjectQuery<T> query)
{
    string name = query.CommandText;
    // See Caveat!
    if (!name.StartsWith("[") || !name.EndsWith("]"))
        throw new Exception("The EntitySet name can only be established if the query has not been modified");
    return name.Substring(1, name.Length - 2);
}

Now the other two methods are completely trivial:

public static void AddObject<T>(
    this ObjectQuery<T> query, T entity)
{
    string set = query.GetEntitySetName();
    query.Context.AddObject(set, entity);
}

public static void Attach<T>(
    this ObjectQuery<T> query, T entity)
{
    string set = query.GetEntitySetName();
    query.Context.AttachTo(set, entity);
}

And we are done. Easy peasy.

*Caveats:

As I said this is a naive solution because it is possible to do something like this:

context.Customer.Where(“it.Name == ‘MSFT’”).Attach(oldCustomer);

and it will fail. This is because the Where(..) call in the above snippet modifies the CommandText of the query, and we have a very naive function for extracting the set from that CommandText.

Still the whole point of this methods is to be easy to use, and it is unlikely you are going write code like that, so maybe a naive solution is just fine anyway.