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.
I added new templating content as methods exposed in a .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.
<#+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>
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_Methods
helper methodThe 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.