Tracking entity dependencies in the Entity Framework
Recently, I’ve been thinking about ways of tracking dependencies in the Entity Framework.
The problem I set out to solve specifically revolves around scenarios where EF Code First techniques are employed. This has a couple of implications, the most important of which are the assumptions that:
All relationships have navigation properties on the dependent end of the relationship
For the purposes of this post, “B depends on A” means that B has a relationship to A of multiplicity (0..n - 1). In other words, each A can be related to 0 or more Bs, but each and every B must be related to exactly 1 A. If an A is deleted, then that delete must be cascaded to any Bs that relate to it (or the deletion of the A must be disallowed).
One of the good things about EF is that it keeps track of entity metadata, so that you can examine your entity model at runtime to discover the relationships between various types of entities. Specifically, the metadata workspace is accessible from the entity context’s underlying object context via IObjectContextAdapter.
The easiest way to find navigation properties is:
ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext; EntityContainer defaultEntityContainer = objectContext .MetadataWorkspace .GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace); IEnumerable<NavigationProperty> navigationProperties = defaultEntityContainer .BaseEntitySets .SelectMany( entitySet => entitySet .ElementType .Members .OfType<NavigationProperty>() );
If we now want to find all navigation properties that link to a particular entity type, we can simply use:
string entityTypeName = typeof(MyEntity).FullName; IEnumerable<NavigationProperty> navigationPropertiesLinkingToMyEntity = navigationProperties .Where( property => // The "To" end of the relationship links to our desired entity type. property.ToEndMember.GetEntityType().FullName == entityTypeName && ( // The "To" end of the relationship is mandatory (lower multiplicity bound of 1). navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || // The "To" end of the relationship mandates delete cascade or delete restriction. navProperty.ToEndMember.DeleteBehavior != OperationAction.None ) );
This gives us a list of entity types and their associated navigation properties that link to a required instance of our dependee entity type. In other words, we now have a list of all entity types that can depend on our entity type, and the means to navigate the relationship between them.
We can now turn this list of navigation properties into a series of Types, PropertyInfos, and LamdbaExpressions for obtaining and comparing entities and key values. I’ll talk more about this in my next post.
I should probably point out that you don’t have to use LINQ or dynamic LINQ predicate expressions to navigate relationships (and, in fact, you cannot use this technique to navigate relationships for which no actual navigation properties exist). You can just (about) as easily navigate the relationships using non-generic methods on the DbContext such as Set(), Entry() and Collection().
Since, however, I am targeting EF Code First, I assume that all relationships have navigation properties (and it does allow us to provide an API which encourages consumers to use it “the EF Code First way”).
In the next post, I will discuss techniques for dynamically querying EF for dependencies of a given entity.