Tip 16 – How to mimic .NET 4.0’s ObjectSet<T> today


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.

Comments (9)

  1. Thank you for submitting this cool story – Trackback from DotNetShoutout

  2. dsoltesz says:

    Even with 3.5 sp1 you don’t have to use the

    ctx.AddObject(“Customers”, newCustomer);

    you can simply say ctx.AddToCustomers(newCustomer);

  3. AlexJ says:

    @dsoltesz

    Absolutely you are right, for add operations, but the point is this is much easier for other operations like Attach(), and it costs very little to extend it to support Add too.

    Also I actually prefer the intellisense experience typing

    ctx.Customers.Add(…

    as opposed to

    ctx.AddToCustomer(…

    where I have to look through all the AddToX(…) methods to get the one I’m after.

    Its a little thing I know but it does make a difference.

    Alex

  4. Meta-Me says:

    What are Stub Entities? A stub entity is a partially populated entity that stands in for the real thing.

  5. VS2010学习 says:

    What are Stub Entities? A stub entity is a partially populated entity that stands in for the real thing

  6. Martin Robins says:

    As an alternative to selecting the entity set name from the query, I created the following extension method based upon the thread at http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3fc700f6-30de-4346-b1ed-09df8c21c273

    public static string GetEntitySetName<T>(this ObjectContext ctx) where T : EntityObject {

            EdmEntityTypeAttribute attribute = ((EdmEntityTypeAttribute[])typeof(T).GetCustomAttributes(typeof(EdmEntityTypeAttribute), true)).Single();
    
            EntityContainer container = ctx.MetadataWorkspace.GetEntityContainer(ctx.DefaultContainerName, DataSpace.CSpace);
    
            return container.BaseEntitySets.Single(es =&gt; es.ElementType.Name == attribute.Name).Name;
    
        }
    
  7. Martin Robins says:

    As an alternative to selecting the entity set name from the query, I created the following extension method based upon the thread at http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3fc700f6-30de-4346-b1ed-09df8c21c273

    public static string GetEntitySetName<T>(this ObjectContext ctx) where T : EntityObject {

    EdmEntityTypeAttribute attribute = ((EdmEntityTypeAttribute[])typeof(T).GetCustomAttributes(typeof(EdmEntityTypeAttribute), true)).Single();
    
    EntityContainer container = ctx.MetadataWorkspace.GetEntityContainer(ctx.DefaultContainerName, DataSpace.CSpace);
    
    return container.BaseEntitySets.Single(es =&gt; es.ElementType.Name == attribute.Name).Name;
    

    }

    this allows the entity set to be determined based upon the attributes in the model and can be used as follows…

    public static void AddObject<T>(this ObjectQuery<T> query, T entity) where T : EntityObject {

    query.Context.AddObject(query.Context.GetEntitySetName&lt;T&gt;(), entity);
    

    }

    or

    public static void Attach<T>(this ObjectQuery<T> query, T entity) where T : EntityObject {

    query.Context.AttachTo(query.Context.GetEntitySetName&lt;T&gt;(), entity);
    

    }

  8. AlexJ says:

    Martin,

    That is pretty cool. And works well with EntityObject. Bear in mind though that your cool solution won’t work if you convert to POCO classes in .NET 4.0

    Did you look at the other implementation of GetEntitySet name that I wrote? The one I did in Tip13. That is more general purpose, and will work in POCO classes too.

    Alex