次の方法で共有


EF Extension Methods Extravaganza Part III – Fun with LINQ to Objects

In this previous post and its follow-on I shared a series of extension methods that I’ve been using to customize the EF a little for some of my scenarios. With this post I’ll wrap-up the short-series (at least until I find another batch that seem worth sharing) by showing a few LINQ to Objects queries I use to extract useful information from the ObjectStateManager. Before we dive in and look at the code, though, let me tell you a bit about the internals of the ObjectStateManager which are mostly interesting from the standpoint of performance tuning. Keep in mind that these are internals and as such they may (in fact quite probably they will) change in future releases. Please do not write code which depends on this information—but it might be useful at some point to just have a little more insight into how things work.

The ObjectStateManager primarily consists of a series of dictionaries which map from an EntityKey to an ObjectStateEntry. Multiple dictionaries are used in order to speed access to the right subset of ObjectStateEntries in certain common scenarios of the framework. Basically there is a separate dictionary for each unique combination of EntityState and type of ObjectStateEntry (ie. whether it is tracking an entity or a relationship). So, there is a Dictionary for added entities, one for modified entities, one for deleted relationships, etc. All of these dictionaries are accessed through a public API which makes them look something like a single union of their contents. You can call ObjectStateManager(EntityState) and pass in a single or bit-flag combination of EntityState values in order to get back an enumeration of all the ObjectStateEntries that match whether they are relationships or entities.

Context.GetEntities(EntityState state)

This method returns all of the entities in the ObjectStateManager associated with a context which match a particular state or set of states. This is just a very simple LINQ to Objects query which retrieves the ObjectStateEntries which match the passed in state and which are not relationships or stubs and then projects out the entity.

public static IEnumerable<IEntityWithKey> GetEntities(this ObjectContext context, EntityState state)

{

    return from e in context.ObjectStateManager.GetObjectStateEntries(state)

           where e.IsRelationship == false && e.Entity != null

           select (IEntityWithKey)e.Entity;

}

Context.GetRelationships(EntityState state)

You can also retrieve each of the relationship entries matching a particular state (even simpler than the above query for entities).

public static IEnumerable<ObjectStateEntry> GetRelationships(this ObjectContext context, EntityState state)

{

    return from e in context.ObjectStateManager.GetObjectStateEntries(state)

           where e.IsRelationship == true

           select e;

}

Context.GetUnchangedManyToManyRelationships()

This one is very specific to some scenarios in the code I’ve been writing recently, so it might not be generally useful to you, but it does demonstrate how to build on the other extension methods to make an ever more “fluent” environment for a particular problem space. If you are new to LINQ it also demonstrates use of LINQ operations using methods and lambdas rather than the query comprehension syntax. The more I use LINQ, the more I find myself hopping between one and the other syntax just depending on what’s most convenient for the scenario.

public static IEnumerable<ObjectStateEntry> GetUnchangedManyToManyRelationships(this ObjectContext context)

{

    return context.GetRelationships(EntityState.Unchanged)

        .Where(e => ((AssociationType)e.EdmType()).IsManyToMany());

}

Context.GetRelationshipForKey(EntityKey key, EntityState state)

This extension method is really quite small, so you might wonder why I’m bothering to include it… Here’s another secret about the internals of the ObjectStateManager, because this is such a convenient kind of data to retrieve for many scenarios when processing graphs of entities, the ObjectStateManager actually maintains a dictionary that contains exactly this list in addition to the other dictionaries. Sadly, that dictionary hasn’t been exposed as a public member yet, so I can’t take advantage of it, but I’m trying to write my code to use it now in the hope that we’ll be able to expose it in a future release of the EF.

public static IEnumerable<ObjectStateEntry> GetRelationshipsForKey(this ObjectContext context, EntityKey key, EntityState state)

{

    return context.GetRelationships(state).Where(e => e.IsRelationshipForKey(key));

}

Context.GetRelationshipsByRelatedEnd(Entity entity, EntityState state)

We’ll wind up with a more interesting query that builds on several of the other methods—this one will retrieve all of the relationship entries for a particular entity and group them by the IRelatedEnd that they match. This is really useful for things like the previous experiment around creating an original values entity graph. The beauty of LINQ group by is that it returns an enumeration of maps from key (for the whole group) to the more specific values which make up the group. So you can easily process the results of this kind of query with a nested loop over the keys and then the entries in the group.

public static IEnumerable<IGrouping<IRelatedEnd, ObjectStateEntry>> GetRelationshipsByRelatedEnd(this ObjectContext context,

    IEntityWithKey entity, EntityState state)

{

    return from entry in context.GetRelationshipsForKey(entity.EntityKey, state)

           group entry by ((IEntityWithRelationships)entity).RelationshipManager

                           .GetRelatedEnd(((AssociationType)(entry.EdmType())).Name,

                                          entry.OtherEndRole(entity.EntityKey));

}

- Danny

Comments

  • Anonymous
    January 28, 2008
    Danny Simmons has been writing a slew of Extension methods which he shares in these three blog posts