Extending EntityFramework Generated Model Classes With T4 Templates

I was working on a project recently where we used a common pattern for versioning DB records. Extending the EF generated model classes made it easier for the team to apply the pattern consistently.
I updated the generated code to render a generic interface if specific properties exist on the model. 2 helper methods (Clone & Update) were added to the models and were regenerated whenever the model was refreshed.

project.png
Figure 1. EnttityVersion.ttinclude

I added new templating content as methods exposed in a .ttinclude file.

t4.png
Figure 2. Add reference to .ttinclude file

Using DB first EF and generating the model from the existing schema uses a t4 template, model1.tt by default.

The .ttinclude file can be added to the top of this template and every model class that is generated will include the new content you define.

RenderInterfaces template method
<#+void Render_Interfaces(System.Data.Entity.Core.Metadata.Edm.EntityTypeBase entity){
var members = ((EntityType)entity).DeclaredProperties;

var pk = entity.KeyMembers.Any(k=>k.Name.EndsWith("SeqID"));
var hasEE = members.Any(m=>m.Name=="EffectiveEndDate");
var hasES = members.Any(m=>m.Name=="EffectiveStartDate");
var hasStatusID = members.Any(m=>m.Name=="StatusID");

#>
:IClonable<<#=entity.Name #>><#+if(hasEE && hasES){#>, IVersionable<<#=entity.Name #>><#+}#><#+if(pk && hasEE && hasES && hasStatusID){#>, IStatusEntity<#+}#>
<#+}#>

The Render_Interfaces method checks for specific columns/properties on the model and if they exist (SeqID, EffectiveEndDate & EffectiveStartDate) then the generated class should have the interfaces IVersionable<T> & IClonable<T>

Adding the Render_Interfaces method to the Model1.tt file
<#=codeStringGenerator.EntityClassOpening(entity)#> <#this.Render_Interfaces(entity);#>

The second method in the .ttinclude template is the one that adds the Clone and Update methods.

Clone and Update method templates
<#+
void Render_Methods(System.Data.Entity.Core.Metadata.Edm.EntityTypeBase entity){

/// <summary>
/// Creates a shallow copy of the <#=entity.Name #>
/// Only copies primative types, should not copy primary keys
/// </summary>
/// <returns>A new clone</returns>
public <#=entity.Name #> Clone(){
	return new <#= entity.Name #>{
    <#+
    for(var i = 0; i < members.Count(); i++){
        if(!entity.KeyMembers.Any(k=>k.Name == members[i].Name)){
		#>
<#=members[i].Name#> = <#=members[i].Name#>,
	<#+}
    }#>
    };
}

/// <summary>
/// Updates the <#=entity.Name #> with values from the source
/// Only updates primative types, should not update primary keys
/// </summary>
public void Update(<#=entity.Name #> source){
    <#+for(var i = 0; i < members.Count(); i++){
        if(!entity.KeyMembers.Any(k=>k.Name == members[i].Name)){#>
		this.<#=members[i].Name#> = source.<#=members[i].Name#>;
	<#+}
    }#>
}
render.png
Figure 3. Calling the Render_Methods helper method

The result of updating the interfaces and adding the Clone & Update methods at the start of the class template looks like this:

public partial class Employee :IClonable<Employee>, IVersionable<Employee>{
/*
    Template debug info
    ---------------------
    Has PK ending with 'SeqID' : True
    Has EE : True
    Has ES : True
    Has Status : False
    */


/// <summary>
/// Creates a shallow copy of the Employee
    /// Only copies primative types, should not copy primary keys
    /// </summary>
    /// <returns>A new clone</returns>
    public Employee Clone(){
    	return new Employee{
        EID = EID,
    	PartyID = PartyID,
    	PreferredRoleID = PreferredRoleID,
    	AccessExpiryDate = AccessExpiryDate,
    	EffectiveStartDate = EffectiveStartDate,
    	EffectiveEndDate = EffectiveEndDate,
    	    };
    }

    /// <summary>
    /// Updates the Employee with values from the source
    /// Only updates primative types, should not update primary keys
    /// </summary>
    public void Update(Employee source){
        this.EID = source.EID;
    	this.PartyID = source.PartyID;
    	this.PreferredRoleID = source.PreferredRoleID;
    	this.AccessExpiryDate = source.AccessExpiryDate;
    	this.EffectiveStartDate = source.EffectiveStartDate;
    	this.EffectiveEndDate = source.EffectiveEndDate;
    	}


    public int SeqId{
    	get{
    	return EmployeePartySeqID;
    	}
    	set{
    	EmployeePartySeqID = value;
    	}
    }

Customizing the generated templates is pretty simple once you get the hang of the default template. I definitely prefer a code first approach, unfortunately that wasn’t an option on this project. While having to work with the edmx isn’t my preference, it is useful to know how to fine tune the models if needs be.

comments powered by Disqus