Jaa


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

Comments

  • Anonymous
    October 20, 2007
    I've had this post kicking around in my inbox for a while but hadn't got round to consuming it. It talks...

  • Anonymous
    December 05, 2007
    Hello Danny, wow thats realy amazing!! is it possible to hang on the Events of Code-generation in Visual Studio 2008 (Beta 2)? (Custom Tool: EntityModelCodeGenerator) Maybe with an AddIn ?

  • Anonymous
    January 11, 2008
    Hi Danny,   This looks exactly like what I have been looking for but I can't get it to work with Visual Studio 2008 and Beta 3 of the Entity Framework.  Did something change?

  • Anonymous
    January 11, 2008
    More info:  The error is on Debug.Assert(generatedCode.Contains(MyAttributeName));

  • Anonymous
    January 11, 2008
    The comment has been removed

  • Anonymous
    January 25, 2008
    In previous posts, I’ve described CSDL annotations , how to extract CSDL from EDMX and introduced you

  • Anonymous
    May 12, 2008
    The comment has been removed

  • Anonymous
    June 11, 2008
    The Entity Framework enables developers to reason about and write queries in terms of the EDM model rather than the logical schema of tables, joins, foreign keys, and so on. Many enterprise systems have multiple applications/databases with varying degrees

  • Anonymous
    August 09, 2008
    Part of the Entity Framework FAQ . 2. Architecture and Patterns 2.1. Does Entity Framework have support

  • Anonymous
    August 27, 2008
    Hi! Code works fine. I would like to do one more thing though. I would like to remove the Browsable(false) attribute from EntityReference properties. Can anybody help?

  • Anonymous
    August 28, 2008
    The mechanisms for customizing codegen in v1 are fairly limited, and I don't know of any way to use them to remove at attribute.  In v2 we hope to create a much more flexible system which would support this kind of thing as well as many more customizations. In the meantime your alternatives are probably something like this:

  1. Modify the output of codegen by hand or with some postprocessing script.  (Not what I would really recommend.)
  2. Use a completely separate mechanism to generate the code yourself--maybe something like t4 to generate from templates.  You could base your templates on what codegen outputs as a starting point and then modify.  This would be a fair amount of work, though.
  3. You could consider creating a property on the partial class which exposes the EntityReference under a different name and not put Browseable(false) on it.  Inconvenient because it has a different name, but probably the easiest thing to do.
  • Danny
  • Anonymous
    August 28, 2008
    You understand of course that none of these alternatives are very elegant, but I guess I'm gonna have to go with one of them. Most probably the first one.