Wednesday, March 23, 2011

Customize Entity Framework Code Generation with T4 Templates

The ADO.NET Entity Framework enables developers to create data access applications by programming against a conceptual application model. In here the logical schema and its mapping with the physical schema is represented by an Entity Data Model (EDM). We can design our Entity Data Model by using Entity Data Model Designer or updating from a database. In both ways it auto generates the relevant code (ObjectContext and various EntityObject classes) in Model.designer.cs file and manual changes to this file may cause unexpected behavior in the application. Also manual changes will be overwritten if the code is regenerated.

But what if we want to do a change to the way file auto-generates? For example in order to XML serialize objects with populated property objects, you might want to remove [XmlIgnoreAttribute()] attribute from the generated code.

Here we can use T4 (Text Template Transformation Toolkit) templates to customize how the ObjectContext and entity classes are created. For the purpose we start with the default T4 Template and then modify it.

This is my simple model:



If we look at the auto generated code, we can see “[XmlIgnoreAttribute()]” as in the following code.

///
/// No Metadata Documentation available.
///

[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("TestT4Model", "FK_Region_RegionType", "Region")]
public EntityCollection Regions
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedCollection("TestT4Model.FK_Region_RegionType", "Region");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)this).RelationshipManager.InitializeRelatedCollection("TestT4Model.FK_Region_RegionType", "Region", value);
}
}
}


Add a T4 Template to the Model:
We can add a T4 Template by Right Click -> Add Code Generation Item… or



By using Add New Item window and selecting ADO.NET EntityObject Generator type.


Then we will get a new item with the .tt extension. Now if we examine Model1.Designer.as file, we can see it is empty. Instead Model1.cs file under Model1.tt file has that generated code.



Now to change the code generation we need to modify Model1.tt template. If we open the Model1.tt file, we can see text which is inside <# #> and text which is not inside <# #> brackets. The text which is inside <# #> is the code to be executed. Text which is not inside the angle brackets will simply be written out to the file. Here is a part of the Model1.tt content.

<# //////// //////// Write Navigation Properties. //////// region.Begin(GetResourceString("Template_RegionNavigationProperties")); foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(n => n.DeclaringType == entity))
{
VerifyGetterAndSetterAccessibilityCompatability(navProperty);
#>

///
/// <#=SummaryComment(navProperty)#>
///
<#=LongDescriptionCommentElement(navProperty, region.CurrentIndentLevel)#>
[XmlIgnoreAttribute()]
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("<#=navProperty.RelationshipType.NamespaceName#>", "<#=navProperty.RelationshipType.Name#>", "<#=navProperty.ToEndMember.Name#>")]
<# if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) { #>

Here if we search for “[XmlIgnoreAttribute()]” attribute we can find it outside the <# #> brackets. Which means it will simply be written out to the file. So I can find and delete those “[XmlIgnoreAttribute()]” attributes where necessary and modified file will be like follows.

<# //////// //////// Write Navigation Properties. //////// region.Begin(GetResourceString("Template_RegionNavigationProperties")); foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(n => n.DeclaringType == entity))
{
VerifyGetterAndSetterAccessibilityCompatability(navProperty);
#>

///
/// <#=SummaryComment(navProperty)#>
///
<#=LongDescriptionCommentElement(navProperty, region.CurrentIndentLevel)#>
[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("<#=navProperty.RelationshipType.NamespaceName#>", "<#=navProperty.RelationshipType.Name#>", "<#=navProperty.ToEndMember.Name#>")]
<# if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) { #>

Then when we save this template, code generation will be happen automatically and “[XmlIgnoreAttribute()]” is not there in the generated code in Model1.cs.

///
/// No Metadata Documentation available.
///

[SoapIgnoreAttribute()]
[DataMemberAttribute()]
[EdmRelationshipNavigationPropertyAttribute("TestT4Model", "FK_Region_RegionType", "RegionType")]
public RegionType RegionType
{
get
{
return ((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference("TestT4Model.FK_Region_RegionType", "RegionType").Value;
}
set
{
((IEntityWithRelationships)this).RelationshipManager.GetRelatedReference("TestT4Model.FK_Region_RegionType", "RegionType").Value = value;
}
}

4 comments:

Anonymous said...

Thank You

It saved me a day.

Anonymous said...

mee too, thanks!

Sujeewa said...

cool t4

Anonymous said...

Thank you. It Helps.