Tip 13 – How to Attach an Entity the easy way


The Problem:

In some of earlier tips we talked about using Attach to load things into the ObjectContext in the unchanged state without wearing the cost of doing a query.

Attach is the weapon of choice if performance is your goal.

Unfortunately our APIs aren’t tailored for the 99% case, which is where you only have just one entity set per type. The Entity Framework supports Multiple Entity Sets per Type or MEST, and the API reflects this, demanding that you provide the name of the EntitySet when you do an attach.

i.e. something like:

ctx.Attach(“Orders”, order);

Now if you are anything like me you probably hate having to specify strings in your code. They are error prone and kind of pollute your code, and it is essentially a “quiz”.

Solution in .NET 4.0:

We’ve fixed this in .NET 4.0 by returning ObjectSet<T> instead of ObjectQuery<T> from our strongly typed properties for each of the EntitySets. ObjectSet<T> has methods for Add, Delete and Attach hanging directly off it, so you can write this:

ctx.Order.Attach(order);

Without a string in sight!

This solution is ideal, you attach to the required set, and whether you have MEST or not, it just works.

Solution for .NET 3.5 SP1:

What about .NET 3.5 SP1 though?

In my opinion we should have provided an generic version of Attach, lets say something like this:

void AttachToDefaultSet<T>(T Entity);

This method would see how many EntitySets exist for T, and if there is just one it would attach the Entity to that set for you. If however there is more than one it would throw an Exception.

While this method doesn’t exist with the power of Extension Methods, it is pretty easy to write.

Here is what we need to do:

  1. Work out the EntityType for <T>
  2. Work out the list of possible EntitySet types that this EntityType could belong to. The EntityType could be a derived type (like Car) that actually belongs to a parent type set (like Vehicles).
  3. Loop over the EntityContainers, and each of the EntityContainer’s EntitySets looking for a match.
  4. If we find one we attach, if not we throw.

So lets do this:

But first a warning, this is demo quality code, I’m a Program Manager not a Developer so use at your own risk 🙂

First we add an extension method to MetadataWorkspace to get the Conceptual Model (C-Space) EntityType for a CLR type (O-Space):

public static EntityType GetCSpaceEntityType<T>(
       this MetadataWorkspace workspace
)
{
    if (workspace == null
        throw new ArgumentNullException(“workspace”);
   
// Make sure the assembly for “T” is loaded
    workspace.LoadFromAssembly(typeof(T).Assembly);
    // Try to get the ospace type and if that is found
    // look for the cspace type too.
    EntityType ospaceEntityType = null;
    StructuralType cspaceEntityType = null;
    if (workspace.TryGetItem<EntityType>(
        typeof(T).FullName, 
        DataSpace.OSpace,
        out ospaceEntityType))
    {
        if (workspace.TryGetEdmSpaceType(
            ospaceEntityType,
            out cspaceEntityType))
        {
            return cspaceEntityType as EntityType;
        }
    }
    return null;
}

Because you could call this code before the metadata for <T> is loaded, one of the first lines makes sure the Assembly for <T> is loaded. This is pretty close to a no-op if the Assembly has already been loaded.

Next we add a method to get the an enumeration of all types we need to match, i.e. the Hierarchy of parent types including the current type:

public static IEnumerable<EntityType> GetHierarchy(
    this EntityType entityType)
{
    if (entityType == null)
        throw new ArgumentNullException(“entityType”);
    while (entityType != null)
    {
        yield return entityType;
        entityType = entityType.BaseType as EntityType;
    }
}

Now we need to do something that takes an EntityType and looks for possible EntitySets, both for that EntityType and parent types:

public static IEnumerable<EntitySet> GetEntitySets(
    this MetadataWorkspace workspace, 
    EntityType type)
{
    if (workspace == null)
        throw new ArgumentNullException(“workspace”);
    if (type == null)
        throw new ArgumentNullException(“type”);

    foreach (EntityType current in type.GetHierarchy())
    {
        foreach (EntityContainer container in 
            workspace.GetItems<EntityContainer>(DataSpace.CSpace))
        {
            foreach (EntitySet set in 
                container.BaseEntitySets.OfType<EntitySet>()
                .Where(e => e.ElementType == current))
            {
                yield return set;
            }
        }
    }
}

And now finally we are ready for the AttachToDefaultSet method:

public static void AttachToDefaultSet<T>(
    this ObjectContext ctx, 
    T entity)
{
    if (ctx== null) throw new ArgumentNullException(“ctx”);
    if (entity == null) throw new ArgumentNullException(“entity”);

 
    MetadataWorkspace wkspace = ctx.MetadataWorkspace;

    EntitySet set = wkspace
       .GetEntitySets(wkspace.GetCSpaceEntityType<T>())
       .Single();

 

    ctx.AttachTo(set.Name, entity);
}

This uses the standard .Single() method, which will throw an exception if there isn’t exactly one possible set for the EntityType.

With this in place we can now write code like this:

Product p = new Product { ID = 1, Name = “Chocolate Fish” } ctx.AttachToDefaultSet(p);

 

Unless of course you use MEST… but you probably don’t!

Extra Credit

While this code should perform okay, it definitely hasn’t optimized in any way. 

It probably makes sense to cache the names of the possible sets for a CLR type, so that everytime you an Attach, you don’t do the same checks, but I’ll leave that as an exercise for you!

Index of Tips

Yes. There is an index of the rest of the tips in this series

Comments (13)

  1. Anonymous says:

    Scenario: In order to make applications perform it makes a lot of sense to cache commonly used reference

  2. Anonymous says:

    Background: In order to be an EF power user today you really need to be familiar with EntitySets . For

  3. kilativ says:

    last line of AttachToDefaultSet you probably mean to use "ctx" not "context" variable.

  4. Alex D James says:

    Kilativ thanks for the catch.

  5. Anonymous says:

    Sometimes rather than writing this: var customer = ctx.Customers.First(c =&gt; c.ID == 5); You would

  6. Anonymous says:

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

  7. Anonymous says:

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

  8. Anonymous says:

    Sometimes rather than writing this: var customer = ctx.Customers.First(c =&gt; c.ID == 5); You would

  9. Anonymous says:

    Background: In order to be an EF power user today you really need to be familiar with EntitySets. For

  10. Anonymous says:

    Scenario: In order to make applications perform it makes a lot of sense to cache commonly used reference

  11. bob.lan@hotmail.com says:

    Should it better to use:

    ctx.Attach(order.EntityKey.EntitySetName, order);

  12. Alex D James says:

    @Bob,

    Well the problem with that code is very often the EntityKey for an Entity is null (until it has been attached). So it is often a sort of chicken and egg problem.

    Alex