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.    

12 comments:

  1. hi,
    i cant compile, because its missing the template for the classes, and in my project is still generating with the ef template. please, can you show the classes template for poco? thank you.

    ReplyDelete
  2. Did you remove the custom tool property from the edmx file?. Please do. Just right click the edmx file, select Properties and in Custom tool property leave it empty. Let me know how it goes.

    ReplyDelete
  3. hi, thank you for the quick response.
    i've created a new project, with the names of the files by default.
    here is what i've done:
    1. new project
    2. add new item - ado.net entity data model
    3. clear property "Custom Tool" from model1.edmx
    (in datasources desapeared the entities autogenerated)

    ReplyDelete
  4. 4. add new item - ado.net poco entity generator
    5. replace content of file "model1.context.tt" with the code of ef4applicationwithpocotemplate.context.tt in this tutorial
    6. in "model1.tt" and "model1.context.tt" replaced
    string inputFile = @"EF4DomainModelForPOCOTemplate.edmx";
    with
    string inputFile = @"Model1.edmx";

    ReplyDelete
  5. 7. save project, and autogenerating of classes begins.

    if i see the content of file "model1.context.cs" it begins with //------------------------------------------------------------------------------
    //
    // This code was generated from MY ADJUSTED TEMPLATE!!!!!!!.
    //
    //------------------------------------------------------------------------------
    and in the code below, for each class generated, an error finding a method called ".AddObject();"

    if i see the content of file "model1.cs" and each class autoenerated, it begins with
    //------------------------------------------------------------------------------
    //
    // This code was generated from a template.
    //
    // Changes to this file may cause incorrect behavior and will be lost if
    // the code is regenerated.
    //
    //------------------------------------------------------------------------------


    what am i doing wrong?

    ReplyDelete
  6. I THINK I FOUND THE PROBLEM.

    1. i think i have a newer version of "t4 entity code generator", and in the newer "model1.tt" template, ther is no AddObject() method anymore.
    2. i assume that the the classes generated will be like the classes generated in the previous post (fields with just get and set).

    do you have a template for generating classes like the previous post? thank you.

    ReplyDelete
  7. I do have the template. Here is the link.
    https://docs.google.com/leaf?id=0B4dbZKyy0BL2MGRlZjhmYzgtMGQzOS00MTEwLTljNTMtOWI5OGVlZjBkYzRl&hl=en

    ReplyDelete
  8. Hi Velarde,

    thanx for this template, it is very very useful.
    Can you please enlighten me, is it advisable to delete the EDMX file after i have completely generated the Codes from the Template? if i delete it, it means if ever i have changes to my database i can either change my generated code manually or delete the generated codes then regenerate it again from step 1...right?

    ReplyDelete
    Replies
    1. Hi John,
      Sorry for late repply. I'm pretty sure you have figure it out already by now, but here it goes...

      You don't have to really delete it. The templates read the EDMX file to generate code. You can just update your model from database and then run templates to generate the code again.

      Again, sorry for late repply.

      Thanks,
      Alexandro

      Delete
  9. I'm not sure if you're still working on this project and I haven't read your priors but if you're open for a suggestion, I'll lay this on you.

    Instead of loading the POCO gen as you are, try this:
    Pin the properties and model browser open so you can watch the change.
    Highlight either the model or the model.store in the model browser tree and notice the code gen strategy is initially set to default.
    Now, Right click on either the model or the model.store in the model browser tree and select add code generation item.

    If you load the POCO gen using this method you will avoid the exception throw during the load process and having to manually correct the edmx address in 2 files.
    Now, as the gen is loading watch the strategy entry change from Default to None.
    This way if you need to turn it back on to use it's function flip it back to default.

    Hope this helps.
    Keep up the good work,

    Rick Z.

    ReplyDelete
  10. <#@ include file="..\..\Templates\DomainModel.Repository.ttinclude"#>

    From where shall i get the above template

    ReplyDelete
    Replies
    1. Hi, here it is..

      https://docs.google.com/leaf?id=0B4dbZKyy0BL2MGRlZjhmYzgtMGQzOS00MTEwLTljNTMtOWI5OGVlZjBkYzRl&hl=en

      Delete