EF CodeGen Events for Fun and Profit (aka How to add custom attributes to my generated classes)

Update 1/11/08: Between beta 2 and beta 3 there was a breaking change in the way events are fired during codegen. The metadata item that is being generated is no longer supplied as the "sender" of the event. Instead it comes in the TypeSource property of the eventArgs passed to the event handler. The code below has been modified to reflect this change. Thanks to hannah39 for pointing out the issue.  

In response to the recent Entity Framework beta 2 release, I've gotten a question or two about how to take advantage of the new CodeGen Events feature, because we don't seem to have any good samples available to help folks wrap their head around this one. Then someone posted a question to the EF forum the other day asking if it's possible to add custom attributes to the generated classes, and I thought, "Aha! Two birds to be taken down with one stone." Even better, I sent a quick message to one of my teammates, Jeff Reed, who responded with a sample that was exactly what I was looking for making my job especially easy.

The first thing you need to know about using CodeGen events is that both edmgen.exe, the commandline tool for things like generating EF classes from a conceptual schema, and the new EF designer integrated with visual studio are built on top of a public API which you can use in your own programs, and when you use the API you can exercise more control over the process. The namespace where this API lives is System.Data.Entity.Design, and the references docs for it are now available online.

The simplest way to use this is to just write a little console app which would replace edmgen.exe for the purpose of generating your classes. Basically you create an instance of EntityClassGenerator, register an event handler for the OnTypeGenerated or OnPropertyGenerated event, and then call the GenerateCode method passing in either two strings with the name of an input file (your CSDL) and an output file (which will contain the generated code) or an XMLReader for input and a TextWriter for output. The event handler receives an eventArgs instance which not only describes the type or property being generated but also contains members which can be modified in order to affect what is output.

So, for a simple example, we can add an attribute called "MyFooAttribute" to the class generated for each Entity type in the CSDL file. The code would look something like this:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Data.Entity.Design;

using System.Data.Metadata.Edm;

using System.Xml;

using System.IO;

using System.Diagnostics;

using System.CodeDom;

namespace AddCustomAttributesToCodeGen

{

    class Program

    {

        const string MyAttributeName = "MyFooAttribute";

        static void Main(string[] args)

        {

            string schema = @"

            <Schema Namespace='CNorthwind' Alias='Self'

                xmlns:cg='schemas.microsoft.com/ado/2006/04/codegeneration'

                xmlns:edm='schemas.microsoft.com/ado/2006/04/edm'

                xmlns='schemas.microsoft.com/ado/2006/04/edm'>

              <EntityType Name='Customer'>

                <Key>

                  <PropertyRef Name='CustomerID' />

                </Key>

                <Property Name='Address' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='City' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='CompanyName' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='ContactName' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='ContactTitle' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='Country' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='CustomerID' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='Fax' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='Phone' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='PostalCode' Type='String' MaxLength='1024' Nullable='false' />

                <Property Name='Region' Type='String' MaxLength='1024' Nullable='false' />

              </EntityType>

              <EntityContainer Name='NorthwindContext'>

                <EntitySet Name='Customers' EntityType='Self.Customer' />

               </EntityContainer>

            </Schema>";

            using (XmlReader reader = XmlReader.Create(new StringReader(schema)))

            {

                StringWriter codeWriter = new StringWriter();

                EntityClassGenerator generator = new EntityClassGenerator();

                generator.OnTypeGenerated += new TypeGeneratedEventHandler(AddAttributeToType);

                IList<EdmSchemaError> errors = generator.GenerateCode(reader, codeWriter);

        string generatedCode = codeWriter.ToString();

               

                // prove that the attribute was generated

                Debug.Assert(generatedCode.Contains(MyAttributeName));

                Console.WriteLine(generatedCode);

            }

        }

        private static void AddAttributeToType(object sender, TypeGeneratedEventArgs eventArgs)

        {

            StructuralType structuralType = eventArgs.TypeSource as StructuralType;

            if (structuralType != null && structuralType.Name.Equals("Customer"))

            {

                CodeAttributeDeclaration attribute = new CodeAttributeDeclaration(MyAttributeName);

                eventArgs.AdditionalAttributes.Add(attribute);

            }

        }

    }

}

If you have Orcas Beta 2 with the EF Beta 2 installed, you can create a new console application, add a reference to system.data.entity.dll and system.data.entity.design.dll and then paste this code into VS and everything should compile and run nicely.

If you decide to play around with this, you'll want to take a look at the documentation for the members of TypeGeneratedEventArgs which will tell you that not only can you add attributes, but you can also set a base type or add interfaces or members to the type. And don't forget to take a look at the docs for PropertyGeneratedEventArgs which shows that you can add attributes to properties as well as additional statements that will appear in the getter or setter of the property. In several cases, these members take CodeDom classes as arguments, and you can find more info about the CodeDom online as well.

  • Danny