Customizing Code Generation in the ADO.NET Entity Designer

 

In previous posts, I’ve described CSDL annotations, how to extract CSDL from EDMX and introduced you to how the ADO.NET Entity Designer generates code.

In this post, I’ll delve into replacing the EntityModelCodeGenerator with a SingleFileGenerator you create. Now, why would you want to do such a thing? Well, there are a number of scenarios where this is interesting and the good news is its fairly straight forward to do and there are lots of samples and documentation to help you get there.

For folks who want to jump directly into the code to see how the pieces fit, this post has links to the sources for you to tweak and reuse in your applications.

Scenario

While the EntityModelCodeGenerator does its job pretty well, it only supports what the ADO.NET Entity Designer supports. This means no code generation support from the designer for things like <Using />, code generation events and CSDL annotations, even though the public generation APIs in the EntityClassGenerator class support them.

Consider the following scenario:

You love the EDM, EF and the Entity Designer but want more control over CLR attributes on the generated classes. Danny’s post describes a nice command line experience for how to use code generation events to add CLR attributes. However, you live in Visual Studio and have gotten used to the Entity Designer experience. In addition, you also want to specify the CLR attributes to be added on a per-type & per-property basis – all within the comfort of Visual Studio.

(Btw, this mirrors a real-life scenario – 3 customers have independently asked me how to do this)

One way to address this scenario is as follows:

1. Cook up a scheme for expressing CLR attributes as annotations in CSDL

2. Create new SingleFileGenerator that:

a. hooks into EF code generation events

b. uses the metadata APIs to get at the annotations in CSDL, and

c. turns them into CLR attributes on the generated code

3. Next, replace EntityModelCodeGenerator with yours by setting the “Custom Tool” property for the EDMX file you want.

SampleEdmxCodeGenerator

Ok, so we’ve introduced all the players and it’s time to start the show. I’ve created a sample SingleFileGenerator (called SampleEdmxCodeGenerator) based on the sample in the VS 2008 SDK that does what I described earlier.

Here’s the pseudo-code for the GenerateCode() override in SampleEdmxCodeGenerator:

protected override byte[] GenerateCode(string inputFileContent)

{

  // Extract CSDL content from EDMX

  // Determine language to generate code in based on the project language

  // Create code generator and wire up code generation events

  // Generate code and log warnings/errors to the Visual Studio error window

  // Convert generated code into bytes to return to Visual Studio

}

 

My code generation event handlers are pretty light and look like this:

private void OnTypeGenerated(object sender,

                     TypeGeneratedEventArgs eventArgs)

{

  eventArgs.AdditionalAttributes.AddRange(

   CreateCodeAttributes(eventArgs.TypeSource)

   );

}

 

private void OnPropertyGenerated(object sender,

                                 PropertyGeneratedEventArgs eventArgs)

{

  eventArgs.AdditionalAttributes.AddRange(

   CreateCodeAttributes(eventArgs.PropertySource)

   );

}

 

The rest of the action is in the CreateCodeAttributes() method. It gets a MetadataItem as parameter and uses Linq over its metadata properties to get all CSDL annotations that start with the custom XML namespace I’ve cooked up.

Here’s what the pseudo-code looks like:

private IList<CodeAttributeDeclaration> CreateCodeAttributes(MetadataItem item)

{

  string xmlns = "https://tempuri.org/SampleAnnotations";

  List<CodeAttributeDeclaration> codeAttributeDeclarations =

   new List<CodeAttributeDeclaration>();

  if (item != null)

  {

    // Get our CSDL annotations from EDM metadata

    IEnumerable<MetadataProperty> metadataProperties =

      item.MetadataProperties.Where(prop => prop.Name.StartsWith(xmlns));

    foreach (MetadataProperty metadataProperty in metadataProperties)

    {

      string metadataPropertyValue = (string)metadataProperty.Value;

      if (!String.IsNullOrEmpty(metadataPropertyValue))

      {

        // Parse the value and create CodeAttributeDeclaration and

        // CodeAttributeArgument

      }

  }

  return codeAttributeDeclarations;

 

The parsing logic in SampleEdmxCodeGenerator is simplistic and expects multiple CLR attributes to be separated by ‘;’ and only supports string and bool attribute parameters. You could easily extend the sample to work with more complicated constructs.

Links to sources for SampleEdmxCodeGenerator including how to build, test, etc can be found here.

 Sanjay Nagamangalam
Program Manager, ADO.NET