Tuesday, June 15, 2010

Entity Framework 4.0 and T4 templates to generate POCO classes


Hi guys, thanks for visiting again.  This post is to have a brief introduction on how to work with T4 templates that generates ObjectContext and POCO classes dynamically.  In this example, we're going customize the context template with a free template editor which is going to make our life easier with it's features.  After we're going to run the test performance.
We are going to proceed as follows:
  • Download ADO.NET C# T4 Entity Code Generator, is an existing T4 template that can be used to generate persistence ignorant entity types (POCO classes) from an Entity Data Model.
  • Install the Tangible T4 Editor for Visual Studio 2010, which is an editor basically adds IntelliSense and Syntax Coloring to T4 Text Templates.
  • Test how this T4 template generates our Context and POCO classes.
  • Run the performance test.
Let's add a class libray project named EF4ApplicationWithPOCOTemplate, just to keep the name convention from my previous post
Let's create our model (edmx file) as we did in the previous post on POCO performance.

In order to have the template available, we need to install the ADO.NET C# POCO Entity Generator.   
After installation is finished, you might need to restart Visual Studio to see the new template available.  Add it to your project from the Add new item menu.
When adding the template you'll be prompt a Security Warning window to confirm the execution of the template.  In this window we choose OK, no because we ignore the message, but because we know it will generate our POCO Classes :-), besides it's going to run every time we save it.
After adding the templates, we'll have an awfull error message related to System.IO.FileNotFoundException
Basically this error message is because it cannot find the Entity Data Model file (edmx file) which provides the source metadata that is used to generate the code for both the POCO classes and the ObjectContextTo fix this issue, we just need to open both files and replace the @"$edmxInputFile$" with the name of our edmx file.
After modifying and saving both template files (.tt) with the correct edmx filename, they will generate the POCO and ObjectContext classes as expected and add them to the project.
Now, we have our project with auto generated POCO and ObjectContext classes that works fine; however, since we want to modify and customize the templates as needed, let's start comparing both versions we have in the solution.  

One is the manually created ObjectContext from the previous post and the new one generated by the T4 template.
As shown, there are a couple of differences between these two classes. 
Let's open the context EF4ApplicationWithPOCOTemplate.Context.tt file.
The first thing I thought when looking at the content of the file is "NOTEPAD".  So, I decided to search for a friendly interface that helps me modify this file.  You'll notice that there are many T4 template editors out there.  For this example, we're going to use a FREE tool named Tangible T4 Template Editor.  It will change the whole appearance of the file content by adding features such as intellisense and syntax coloring.

I will only select to Install T4 Editor this time.  If you want, you can install the Modeling tools as well. 
After installing this tool and restarting Visual Studio, you can manage the installation with the Extension Manager, which is in the Tools menu.
Now we're ready to open the template file and change it using the features added.
For this example, I have changed the template so it generates the exact same code, for the ObjectContext, as the manually POCO generated class from the previous post.
File modified content: EF4ApplicationWithPOCOTemplate.Context.tt (all content)
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ include file="EF.Utility.CS.ttinclude"#><#@
 output extension=".cs"#><#

CodeGenerationTools code = new CodeGenerationTools(this);
MetadataTools ef = new MetadataTools(this);
MetadataLoader loader = new MetadataLoader(this);

string inputFile = @"EF4DomainModelForPOCOTemplate.edmx";
EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile);
string namespaceName = code.VsNamespaceSuggestion();

EntityContainer container = ItemCollection.GetItems<EntityContainer>().FirstOrDefault();
if (container == null)
{
    return "// No EntityContainer exists in the model, so no code was generated";
}
#>
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated from MY ADJUSTED TEMPLATE!!!!!!!.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Data.Objects;
using System.Data.EntityClient;

<#
if (!String.IsNullOrEmpty(namespaceName))
{
#>
namespace <#=code.EscapeNamespace(namespaceName)#>
{
<#
    PushIndent(CodeRegion.GetIndent(1));
}
#>
<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : ObjectContext
{
    public <#=code.Escape(container)#>() : base("name=<#=container.Name#>", "<#=container.Name#>")
    {
<#
        WriteLazyLoadingEnabled(container);
#>
    }
<#
        foreach (EntitySet entitySet in container.BaseEntitySets.OfType<EntitySet>())
        {
#>

    private ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.FieldName(entitySet)#>;
    <#=Accessibility.ForReadOnlyProperty(entitySet)#> ObjectSet<<#=code.Escape(entitySet.ElementType)#>> <#=code.Escape(entitySet)#>
    {
        get { 
                if(<#=code.FieldName(entitySet) #>==null) 
                    <#=code.FieldName(entitySet)#> = CreateObjectSet<<#=code.Escape(entitySet.ElementType)#>>("<#=entitySet.Name#>"); 
                return <#=code.FieldName(entitySet) #>;
            }
    }
    
    public void AddTo<#=code.Escape(entitySet)#>(<#=code.Escape(entitySet.ElementType)#> <#=code.Escape(entitySet).ToLower().Trim('s') #>){
        <#=entitySet.Name#>.AddObject(<#=code.Escape(entitySet).ToLower().Trim('s') #>);
    }
<#
        }
#>
}
<#
if (!String.IsNullOrEmpty(namespaceName))
{
    PopIndent();
#>
}
<#
}
#>
<#+
private void WriteLazyLoadingEnabled(EntityContainer container)
{
   string lazyLoadingAttributeValue = null;
   string lazyLoadingAttributeName = MetadataConstants.EDM_ANNOTATION_09_02 + ":LazyLoadingEnabled";
   if(MetadataTools.TryGetStringMetadataPropertySetting(container, lazyLoadingAttributeName, out lazyLoadingAttributeValue))
   {
       bool isLazyLoading = false;
       if(bool.TryParse(lazyLoadingAttributeValue, out isLazyLoading))
       {
#>
        this.ContextOptions.LazyLoadingEnabled = <#=isLazyLoading.ToString().ToLowerInvariant()#>;
<#+
       }
   }
}
#>

To test the generated results, I've just copied and pasted the folder that contains the class with CRUD operations from previous post, then renamed the file and class.  After just add a couple of lines in the console application to run it.
After running the application I have the following results.
Conclusions
  • The C# T4 Entity Code Generator for POCO works great and the best thing is that it can be customized as needed. 
  • The Tangible T4 Editor is a good tool to modify these files.
  • The performance was increased.
What's next?
I'm thinking on writing about extending performance test for EF 4.0, refactoring current solution, repository pattern implementation, MVC 2 pattern, WCF Data Services, etc.  I'm always open to suggestions.