Customizing T4 Templates
The information in this post is out of date.
Visit msdn.com/data/ef for the latest information on current and past releases of EF.
The whole point of us using T4 for code-generation is that it makes it easy to customize the generated entities.
A good example might be to support some sort of validation when setting string properties.
To illustrate lets re-use the code from the recent POCO template walkthrough post.
In that project there is a Person entity which has an EmailAddress property. If we want this property to only accept valid EmailAddresses, we need to modify the generated code to do some validation, probably using a Regular Expression.
One way to do this is to use Structural Annotations in conjunction with a customized T4 template. Structural Annotations, if you aren’t familiar with them, allow you to put custom annotations in the model and retrieve them using the Entity Frameworks metadata APIs. So we can use Structural Annotations to embed our Regular Expressions right in the model, and access them from inside the T4 template, which already uses the Entity Framework’s metadata APIs.
To do this, the first step is to register a custom namespace in the schema element of the CSDL portion of the EDMX file:
<edmx:ConceptualModels>
<Schema xmlns="https://schemas.microsoft.com/ado/2008/09/edm" xmlns:store="https://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator"
xmlns:Regex="https://Regex"
Namespace="Blogging" Alias="Self">
<EntityContainer Name="BloggingContainer" >
<EntitySet Name="Blogs" EntityType="Blogging.Blog" />
<EntitySet Name="People" EntityType="Blogging.Person" />
<EntitySet Name="Entrys" EntityType="Blogging.Entry" />
With that in place we can put the Regular Expression on our EmailAddress property by putting some custom XML, in the namespace we just registered, inside the corresponding Property.
Something like this:
<EntityType Name="Person">
<Key>
<PropertyRef Name="ID" /></Key>
<Property Type="Int32" Name="ID" Nullable="false" store:StoreGeneratedPattern="Identity" />
<Property Type="String" Name="Firstname" Nullable="false" MaxLength="50" />
<Property Type="String" Name="Surname" Nullable="false" MaxLength="50" />
<Property Type="String" Name="EmailAddress" Nullable="false" MaxLength="100">
<Regex:Expression ErrorMessage="A valid emailaddress is required">^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$</Regex:Expression>
</Property>
<NavigationProperty Name="Entries" Relationship="Blogging.PostPerson" FromRole="Person" ToRole="Post" />
<NavigationProperty Name="Blogs" Relationship="Blogging.PersonBlog" FromRole="Person" ToRole="Blog" />
</EntityType>
I’m using this regular expression: “ ^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9] +@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$ ” but the of course any Regular expression would work.
The ErrorMessage attribute is there so it can be used as the Message of an Exception in the event of a non-match.
Once you have this Regular Expression in your model, you can modify your T4 template (in this case the template is the POCO Types template that ships with the Microsoft Entity Framework Feature CTP 1), so that it looks for and handles Regular Expressions.
So rather than this:
public string EmailAddress {
get
;
set
; }
We want to produce something like this:
private string _EmailAddress;
private
Regex
_EmailAddressRegex = new
Regex
(@"^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$");
public string EmailAddress
{
get
{ return _EmailAddress; }
set
{
if (value != null && !_EmailAddressRegex.IsMatch(value))
throw new Exception("A valid emailaddress is required");
_EmailAddress = value;
}
}
To make this change to the generated code there are three parts of the template that need to change:
-
- A change to the part of the template that emits primitive properties:
- <#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>
-
- {
-
- <#
-
- region.Begin("Primitive Properties");
-
- foreach (EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType
-
- && p.DeclaringType == entity))
-
- { #>
-
- <# if (HasRegex(edmProperty)) { #>
-
- private <#=code.Escape(edmProperty.TypeUsage)#> _<#=code.Escape(edmProperty)#>;
-
- private Regex _<#=code.Escape(edmProperty)#>Regex = new Regex(@"<#= GetRegexElement(edmProperty).Value #>");
-
- <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>
-
- {
-
- <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return _<#=code.Escape(edmProperty)#>; }
-
- <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set
-
- {
-
- if (value != null && !_<#=code.Escape(edmProperty)#>Regex.IsMatch(value))
-
- throw new Exception("<#=GetRegexErrorMessage(edmProperty)#>");
-
- _<#=code.Escape(edmProperty)#> = value;
-
- }
-
- }
-
- <# } else { #>
-
- <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#> {
-
- <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get; <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set;
-
- }
-
- <# }
-
- }
-
- region.End();
-
- #>
-
- Some utility functions (used above), that use the Entity Framework Metadata APIs to access the Regular Expression:
MetadataProperty GetRegexProperty(EdmProperty property)
{
return property.MetadataProperties.FirstOrDefault(mp => mp.Name == "https://Regex:Expression");
}
bool HasRegex(EdmProperty property)
{
return GetRegexProperty(property) != null;
}
System.Xml.Linq.XElement GetRegexElement(EdmProperty property)
{
var prop = GetRegexProperty(property);
if (prop == null) throw new Exception("No Regex found");
var node = prop.Value as System.Xml.Linq.XElement;
if (node == null) throw new Exception("No Regex found");
return node;
}
string GetRegexErrorMessage(EdmProperty property)
{
var node = GetRegexElement(property);
var messageAttr = node.Attribute("ErrorMessage");
if (messageAttr == null)
return "Regular Expression check for " + property.Name + " failed.";
else
return messageAttr.Value;
}
-
- And something to make the generated classes use System.Text.RegularExpressions:
<#
void WriteHeader(string namespaceName, CodeGenerationTools code, params string[] extraUsings)
{
CodeRegion region = new CodeRegion(this);
#>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
Now if you replace your POCO Types T4 template with the template attached below, the generated classes will enforce any regular expressions you add to your model. To find the changes in the T4 template just find ‘Regex’ and ‘RegularExpression’.
This is just scratching the surface though, you could really run with this idea, perhaps producing DataAnnotations directly from the EDM so that they can be used by frameworks like Dynamic Data?
Your options are almost unlimited.
Have fun!
Alex James
Entity Framework Team
Comments
Anonymous
July 22, 2009
Can you guys put your EF 4.0 T4 templates on CodePlex? That way new features like this can be added to them, not only by you guys, but by other community members? That would be fantastic! Thanks a bunch! -RobertAnonymous
July 23, 2009
You know what I'd like to see more than anything? I'd like to see the generated code have the [GeneratedCode] attribute, so that FxCop doesn't barf on it. Sure, I could do this by customizing the T4 but I'd be happy if it shipped that way.Anonymous
July 24, 2009
Oh, and that's especially true for view generation, which we can't customize.Anonymous
July 24, 2009
@Craig I'll take the [GeneratedCode] idea back to the team. Seems like a good idea. AlexAnonymous
July 24, 2009
Thank you! I have a huge and difficult-to-maintain file of FxCop exceptions I could delete if this were implemented.Anonymous
July 25, 2009
@Robert, The tt file is now attached (it was meant to be all along) so feel free to put it or some derivative of it up on CodePlex. AlexAnonymous
December 10, 2009
Hi! I'm trying to accomplich DataAnnotations as suggested in the article but I'm afraid I can't figure out how to get to the MaxLength attribute of a Property. I would like to be able to automatically limit the number of chars in a text field. Any suggestions? Thanks FredrikAnonymous
April 30, 2010
The comment has been removedAnonymous
January 13, 2011
The T4 templates are sitting on your machine so you can modify them. You can open them up and add your attributes easily.