EntityRoots – An EF Extensibility Exercise

A little while back I was working on a project for one of the other teams in the Data and Modeling Group where we were building an EDM which was not huge but nevertheless had a number of entity sets, and I was trying various strategies to make the model more usable and easier to understand.  It occurred to me that one thing which would help would be to remove some of the entity sets which would normally be exposed as ObjectSet properties on the context since it was very unusual to want to reference those sets directly at the top level—normally all queries and other operations would be initiated using other sets/types.  Those types effectively were the “roots” of interaction with the model.  This concept is certainly weaker but somewhat reminiscent of aggregate roots as defined by practitioners of domain driven design.

This is not all that different from some samples we have put together before.  Sanjay, for instance, wrote a post quite some time ago showing how to enable an extension which would cause code generation to add an arbitrary attribute to classes or properties.  The core capability of adding annotations to the conceptual model metadata as described in that post is still the same.  Since EF4 has shipped, however, we have some additional options which make the overall task a whole lot easier than it was before.  First off, this seemed like a great opportunity to play a bit with the EDM Designer Extension Starter Kit which I had not previously given the time that it deserves.  Of course this is a perfect application for T4 templates which make it much easier to customize code generation (I’ll not give a link for that since we have talked about it a lot in the past—you can easily find more info with a quick search), and the VS2010 VSIX extension format makes it easy to wrap all of this up in a neat package and manage installation and versioning of it.

The final result of this effort is a VSIX package I call EntityRoots which adds an extension to the designer making it possible to set in the properties for an entity type whether or not it is a root which will then show up as an annotation in the CSDL for that entity type as well as a new T4 code generation item template which looks at that annotation in order to decide whether or not to output an ObjectSet property for that type.  In addition to just downloading and using the VSIX package, if you want you can download the solution I use to build it.

For the designer extension, the exercise was mostly just a matter of removing parts of the starter sample that do other things than just adding a property to entity types and renaming things, so I won’t spend much time describing how to do it.  You can follow the example and easily adapt it to whatever other scenario you might have.  Happily the starter kit also automatically creates a VSIX project to install the extension without much effort.  The more interesting part (although also pretty easy) is creating the T4 template, turning it into a VS item template and adding it to the VSIX package.  To do that, my first step was to decide that I wanted to base my template on the POCO template.  You could, of course, do the same thing with the default code gen template, the self-tracking entities template or any other EF code gen template you might have. 

I created a small sample project, added the POCO template, and then just added a few lines to the portion of the template which outputs the ObjectSet properties:

         var entityRootsExist = container.BaseEntitySets.OfType<EntitySet>()
            .Where(s => s.ElementType.MetadataProperties
                .Contains("blogs.msdn.com/dsimmons/schemas/EntityRoot:EntityRoot"))
            .Any();

        foreach (EntitySet entitySet in container.BaseEntitySets.OfType<EntitySet>())
        {
            if (!entityRootsExist || (entitySet.ElementType.MetadataProperties
                .Contains("blogs.msdn.com/dsimmons/schemas/EntityRoot:EntityRoot") &&
                ((XElement) entitySet.ElementType
                .MetadataProperties["blogs.msdn.com/dsimmons/schemas/EntityRoot:EntityRoot"].Value).Value == "True"))
            {
#>

    <#=Accessibility.ForReadOnlyProperty(entitySet)#> ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.Escape(entitySet)#>
    {
        get { return <#=code.FieldName(entitySet) #>  
                    ?? (<#=code.FieldName(entitySet)#> = CreateObjectSet<<#=code.Escape(entitySet.ElementType)#>>("<#=entitySet.Name#>")); }
    }
    private ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.FieldName(entitySet)#>;
<#

            }
        }

Essentially we just check to see if any of the entity sets have the annotation.  If so, then we only output an ObjectSet if that set has the annotation and it is set to true.

I then put the modified templates into a project which creates a VS item template (which is just a zip file with the T4 template files plus a vstemplate file giving information about how the templates should be added to the project).  In order to make this item template show up in the EF designer’s add code generation item dialog, the vstemplate file’s name must begin with “ADONETArtifactGenerator_”.  You can read more about VS item templates here.

- Danny