Ask Kathleen

Customize Code Generation in EF

Learn how to exclude attributes from the code that Entity Framework generates for you automatically.

Technologies mentioned in this article include the Entity Framework, ADO.NET, VB.NET, and C#.

Q
I'm working with Entity Framework (EF), and I've run into a problem. All of the generated properties are marked with the DataMember attribute, which means they're included in the data contract. Some of the properties shouldn't be in the data contract. I've heard you can customize the code generation, which leads to the question: Can I remove these attributes?

A
EF uses the standard Visual Studio Custom Tool mechanism and Single File Generator. This infrastructure allows a Custom Tool, generally a code generator, to be attached to any loaded file. If you open a new EF model, you'll see EntityModelCodeGenerator as the default custom tool. To customize code generation, you replace this custom tool with your own version. EntityCodeModelGenerator uses the EntityClassGenerator under the hood to perform code generation. You can either create a replacement generation system, or take the easier approach of wrapping and extending the EntityClassGenerator.

The EntityClassGenerator includes several documented extensibility points that let you perform tasks in your wrapper, such as adding attributes. For example, you can add the Debuggable attribute with False as the parameter if you don't want to step through the properties in the debugger.

Unfortunately, the extensibility model of code generation for EF doesn't support removing attributes. However, you can use the known extensibility points to trick EF. I'll show you how to modify the EDMX mapping file and replace the standard code generation to remove the DataMember attributes on specific properties that don't belong in your data contract. Along the way, I'll also explain how to add new attributes if you wish.

The first step is to add an annotation as an XML attribute in the .EDMX file. This tells the generator which properties shouldn't include the DataMember attribute. The trick is to catch the output stream after generation, but before the designer file is output. You can parse this stream and remove the undesired attributes. The easiest way to know which properties shouldn't have the DataMember attribute during parsing is to include an additional dummy CLR attribute for each property that shouldn't have a DataMember attribute. The parse step removes both the dummy CLR attribute and the DataMember attribute.

The ADO.NET team has provided a SampleEdmxCodeGenerator (available here) that adds CLR attributes specified in the .EDMX file. In the conceptual model of the EDMX you can specify which CLR attributes you want to appear on types or properties in the generated code. The sample does a good job of isolating the yucky details of exposing the custom generator to Visual Studio by placing these details in the BaseCodeGenerator and BaseCodeGenratorWithSite classes.

However, it does a poor job of isolating the standard generation process from your customizations, so I'll replace the SampleEdmxCodeGenerator class with a new class derived from BaseCodeGeneratorWithSite. Placing this new class in a separate assembly makes it easier to keep the base classes synchronized as updates appear on CodePlex and lets you shift to Visual Basic, which makes the XML parsing easier. A third assembly contains the customization that you can replace with your own customizations. This design means your customization class has at most three tasks: attach handlers for the OnTypeGenerated and OnPropertyGenerated events, provide the handler implementation, and override the AlterStream method (see Figure 1). The rest of the generation process leverages reusable code.

Keep the Source Clear
An important principle in working with metadata is to ensure that the source of the metadata remains clear. You can also think of this as retaining knowledge of who's responsible for a particular metadata element or attribute. If you add an attribute in the EDMX namespace and do not specify your own namespace, it will appear to belong to the model itself and might conflict with a future attribute. The team's SampleEdmxCodeGenerator adds a namespace, but it uses tempuri.org, which is nothing more than a marker indicating where you should insert your own namespace. If you use the same annotation pattern and someone later switches to a different generator, your dummy attribute might be emitted as a CLR attribute in the absence of additional parsing. The resulting code wouldn't compile because the dummy attribute doesn't exist. To avoid conflicts like this, put the new attribute in its own namespace:

<Property Name="Fax" Type="String" _
    MaxLength="24" xmlns:kad= _
    "http://kadgen.com/edmx/Annotations" 
    kad:Instructions="NoDataMemberAttribute" />

You need to balance concern for avoiding conflicts against the desire for standard metadata used across many generators, so I'll also support the SampleEdmxCodeGeneration attributes on either the EntityType or Property elements:

<EntityType Name="Customers"
xmlns:attr="http://tempuri.org/ _
	SampleAnnotations" attr:ClrAttributes= _
	"System.ComponentModel.Description( _
	" Customer Desc.");
	System.ComponentModel.Browsable(false)">

This element illustrates how important it is to establish a standard set of metadata attributes for extended generators. Elements and attributes that aren't in the core EDM and EDMX namespaces are ignored by the designer and standard generators. You can make these changes for your own .EDMX files by right-clicking on the file name in Solution Explorer, selecting Open With, and choosing XML Editor. You'll find sample .EDMX files in the download.

Under this architecture, the class performing customized generation overrides the AttachHandlers method to supply handlers for the OnPropertyGenerated and/or OnTypeGenerated methods:

Protected Overrides Sub AttachHandlers( _
    ByVal classGenerator As EntityClassGenerator)
AddHandler classGenerator.OnPropertyGenerated, _
    AddressOf OnPropertyGenerated
End Sub

The event argument includes an AdditionalAttributes property. Any attributes you add to this list will be included in the output:

Private Sub OnPropertyGenerated(ByVal 
  sender As Object, _
    ByVal eventArgs As PropertyGeneratedEventArgs)
eventArgs.AdditionalAttributes.AddRange( _
    CreateCodeAttributes(eventArgs.PropertySource))
End Sub

Using AddRange lets you pass in an IList, which can be empty.

In addition to attributes, you can add Get and Set statements and adjust the return type, as long as the types are compatible. Items you add must be CodeDOM fragments. The CodeDOM is verbose and sometime messy to work with; it requires two lines of code to create a simple attribute with a parameter value:

Dim attr = New CodeAttributeDeclaration( _
    "System.Diagnostics.Debuggable")
attr.Arguments.Add(New CodeAttributeArgument( _
    New CodePrimitiveExpression(False)))

You can expand the same techniques to add attributes based on annotations defined in your .EDMX file. The EDMX metadata is available through the PropertySource of the event arguments. In addition to transferring attributes, you might want to add Get and Set statements based on XML attributes. You can find the code to include CLR attributes in the download. This code parses the ClrAttributes annotation value and calls the GetNewClrAttribute method for each requested attribute:

codeAttributeDeclarations.Add(GetNewClrAttribute( _
    attributeName, params.ToArray)) 

GetNewClrAttribute provides a wrapper for the CodeDOM creation of the attribute. Placing this code in the base class allows you to reuse the functionality easily. This code also makes it easy to add an attribute, such as the dummy attribute that marks the properties that shouldn't have the DataMember CLR attribute. If you left the code untouched at this point, the output .NET code would be:

<Global.System.Data.Objects.DataClasses. _
    EdmScalarPropertyAttribute(), _
    Global.System.Runtime.Serialization. _
    DataMemberAttribute(), _
    RemoveDataMemberAttribute ()> 
    Public Property Fax() As String

The output .NET code won't compile at this point because the dummy attribute doesn't exist. So, there is one more step you must complete. The generation code in the base class calls an overridable method named AlterStream that produces the final output string. The generator code calls AlterStream and this is your last chance to adjust the code before it is output:

Dim errors As IList(Of EdmSchemaError) = _
    classGenerator.GenerateCode(csdlReader, codeWriter)
ReportErrors(errors)
Dim code = AlterStream( _
    codeWriter.ToString(), languageOption)

Override AlterStream
The default implementation of AlterStream does nothing more than return the string. You can override AlterStream and parse the output stream to find the dummy CLR attributes that mark which properties shouldn't include DataMembers. You need to remove both the dummy CLR attribute and the DataMember attributes (see Listing 1). Note the parse is dependent on whether the output language is Visual Basic or C# and parsing the stream has a few challenges. C# outputs attributes one to a line, while VB uses a comma delimited attribute list. It's also a bit tricky to correct vertical and horizontal whitespace. The approach you'll see in the sample download is to access the string through the ReadLine method of a StringReader and output to a StringBuilder to maintain performance with large files. You can improve performance by setting the capacity of the StringBuilder to the length of the full code string. This avoids reallocation as the output is constructed.

To use your completed generator, you need to hook it up to Visual Studio. You need to place each assembly into the GAC and register your generator with Visual Studio, which requires setting it under two registry keys. The GAC is the Global Assembly Cache, which provides a common location for shared files. You can place the assembly in the GAC with a command file that contains this snippet for each assembly:

gacutil /i 
MyEfCodeGenerator\bin\DebugSpecificAttributeGenerator.dll
reg import MyEfCodeGenerator\RegisterWithVS.reg

The assembly must also be registered for COM.

You prepare your assembly for COM in the Project Properties. You also must provide each COM visible class with a GUID defined as the class ID. You do that with code similar to this:

<ComVisible(True)> _
<Guid("EBBBDCCF-00A5-4e15-8028-8DC313DEA6CE")> _
Public Class SpecificAttributeGenerator
   Inherits EFCodeGeneration. _
   MyEdmxCodeGeneratorBase

Note that you should use the Tools/Create GUID tool or another approach to create a unique GUID for classes and assemblies in your own version of the generator.

The REG file for each assembly is straightforward (see Listing 2). Note that the yellow highlights in the listing mark the three locations where you must enter the same GUID you used for the class ID. If you create a new assembly or change the version, you must also alter the Assembly and Class names, which are marked in purple. The name used for the Custom Tool value in the designer appears twice, and I've marked this in green. This name must be unique among the generators registered on your system. Getting the GAC and registry settings just right can be a pain, so use a command file to register and unregister your generators -- the online download includes files to get you started.

Once the command and REG files are ready, open a Visual Studio command prompt (as Administrator if you're running Vista) and navigate to the solution root. Run the registration command script (Register.cmd in the download) to register your generator. You can unregister the generator and remove it from the GAC using an Unregister command file that contains code like this:

gacutil /u SpecificAttributeGenerator

reg delete HKLM\SOFTWARE\Microsoft\VisualStu
dio\9.0\CLSID\{EBBBDCCF-00A5-4e15-8028-8DC313DEA6CE}
reg delete HKLM\SOFTWARE\Microsoft\VisualStu
dio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}\Sp
  ecificAttributeGenerator
reg delete HKLM\SOFTWARE\Microsoft\VisualStudio\9.0\Gen
erators\{164B10B9-B200-11D0-8C61-00A0C91E29D5}\Specifi
cAttributeGenerator

The coloring again indicates the values you might need to change.

Once you complete the registration, you can use your custom generator. Create or open a project with an .EDMX file in Solution Explorer. Select the .EDMX file, right-click on it, select properties, and enter the name you used to register the generator (highlighted in green in Listing 2) as the Custom Tool. Your tool will run, and you can explicitly re-run it by right clicking on the .EDMX file in Solution Explorer and selecting "Run Custom Tool." If you get a message that the tool can't be found, you have a typo or a registration problem.

Like any other code, your custom generator can have problems or bugs. To debug the code generator, open it in one instance of Visual Studio. Set the generator as the startup project, and set the Start Action to Start External Program on the Debug tab. The external program to start is devenv.exe, which by default you can find in <program files>\Microsoft Visual Studio 9.0\Common7\IDE. Set breakpoints in your code and Start Debugging (F5). You'll see another instance of Visual Studio open. In the second instance, open the project that contains your test .EDMX file. Set the Custom Tool if you haven't already done so -- this runs the generator. If you have already set the tool, right-click on the EDMX file and select Run Custom Tool. The breakpoints in the first instance of Visual Studio will be active, and you can stop or step through your code.

It might seem backward to add an attribute to remove another attribute, but this is a good way to place a marker in the generation stream to identify the locations of items that you need to change in the generated output.

About the Author

Kathleen is a consultant, author, trainer and speaker. She’s been a Microsoft MVP for 10 years and is an active member of the INETA Speaker’s Bureau where she receives high marks for her talks. She wrote "Code Generation in Microsoft .NET" (Apress) and often speaks at industry conferences and local user groups around the U.S. Kathleen is the founder and principal of GenDotNet and continues to research code generation and metadata as well as leveraging new technologies springing forth in .NET 3.5. Her passion is helping programmers be smarter in how they develop and consume the range of new technologies, but at the end of the day, she’s a coder writing applications just like you. Reach her at [email protected].

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube