T4 Templates
JD.Efcpt.Build supports T4 (Text Template Transformation Toolkit) templates for customizing code generation. This guide explains how to use and customize templates.
Overview
T4 templates let you control exactly how your DbContext and entity classes are generated. You can:
- Change the coding style and formatting
- Add custom attributes or annotations
- Include additional methods or properties
- Generate partial classes with custom logic
- Apply your organization's coding standards
Enabling T4 Templates
Step 1: Enable in Configuration
Add to your efcpt-config.json:
{
"code-generation": {
"use-t4": true,
"t4-template-path": "."
}
}
The t4-template-path is relative to the configuration file location.
Step 2: Create Template Directory
Create the template folder structure in your project:
YourProject/
├── YourProject.csproj
├── efcpt-config.json
└── Template/
└── CodeTemplates/
└── EFCore/
├── DbContext.t4
└── EntityType.t4
Or use a simpler structure:
YourProject/
├── YourProject.csproj
├── efcpt-config.json
└── CodeTemplates/
└── EFCore/
├── DbContext.t4
└── EntityType.t4
Step 3: Add Template Files
Copy the default templates from EF Core Power Tools or create your own. The minimum required templates are:
DbContext.t4- Generates the DbContext classEntityType.t4- Generates entity classes
Template Structure
The StageEfcptInputs task understands several common layouts:
Layout 1: Template/CodeTemplates/EFCore
Template/
└── CodeTemplates/
└── EFCore/
├── DbContext.t4
└── EntityType.t4
The task copies CodeTemplates to the staging directory.
Layout 2: CodeTemplates/EFCore
CodeTemplates/
└── EFCore/
├── DbContext.t4
└── EntityType.t4
The entire CodeTemplates tree is copied.
Layout 3: Custom folder without CodeTemplates
MyTemplates/
├── DbContext.t4
└── EntityType.t4
The folder is staged as CodeTemplates.
Template Staging
During build, templates are staged to:
obj/efcpt/Generated/CodeTemplates/EFCore/
├── DbContext.t4
└── EntityType.t4
This ensures:
- Consistent paths for efcpt CLI
- Clean separation from source templates
- Correct fingerprinting for incremental builds
Customizing Templates
DbContext Template
The DbContext.t4 template generates your DbContext class. Key customization points:
Adding custom using statements:
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using MyApp.Common; // Add your custom using
Adding custom methods:
<#
foreach (var entityType in Model.GetEntityTypes())
{
#>
public DbSet<<#= entityType.Name #>> <#= entityType.GetDbSetName() #> => Set<<#= entityType.Name #>>();
<#
}
#>
// Custom method
public async Task<int> SaveChangesWithAuditAsync(CancellationToken cancellationToken = default)
{
// Add audit logic
return await SaveChangesAsync(cancellationToken);
}
EntityType Template
The EntityType.t4 template generates entity classes. Common customizations:
Adding custom attributes:
<#
var displayName = property.GetDisplayName();
if (!string.IsNullOrEmpty(displayName))
{
#>
[Display(Name = "<#= displayName #>")]
<#
}
#>
public <#= code.Reference(property.ClrType) #> <#= property.Name #> { get; set; }
Generating partial classes:
namespace <#= code.Namespace(entityType.GetNamespace(), Model) #>
{
public partial class <#= entityType.Name #>
{
// Generated properties
<#
foreach (var property in entityType.GetProperties())
{
#>
public <#= code.Reference(property.ClrType) #> <#= property.Name #> { get; set; }
<#
}
#>
}
}
Template Configuration
Setting Template Path
In .csproj:
<PropertyGroup>
<EfcptTemplateDir>CustomTemplates</EfcptTemplateDir>
</PropertyGroup>
Or in efcpt-config.json:
{
"code-generation": {
"use-t4": true,
"t4-template-path": "CustomTemplates"
}
}
Resolution Order
Templates are resolved in this order:
<EfcptTemplateDir>property (if set)Templatedirectory in project directoryTemplatedirectory in solution directory- Package default templates
Common Customizations
Adding XML Documentation
/// <summary>
/// Gets or sets the <#= property.GetDisplayName() ?? property.Name #>.
/// </summary>
<#
if (property.GetComment() != null)
{
#>
/// <remarks>
/// <#= property.GetComment() #>
/// </remarks>
<#
}
#>
public <#= code.Reference(property.ClrType) #> <#= property.Name #> { get; set; }
Adding Interface Implementation
namespace <#= code.Namespace(entityType.GetNamespace(), Model) #>
{
public partial class <#= entityType.Name #> : IEntity
{
// ... properties
}
}
Custom Naming Conventions
<#
// Convert to camelCase for private fields
var fieldName = "_" + char.ToLower(property.Name[0]) + property.Name.Substring(1);
#>
private <#= code.Reference(property.ClrType) #> <#= fieldName #>;
public <#= code.Reference(property.ClrType) #> <#= property.Name #>
{
get => <#= fieldName #>;
set => <#= fieldName #> = value;
}
Adding Validation Attributes
<#
var maxLength = property.GetMaxLength();
if (maxLength.HasValue)
{
#>
[MaxLength(<#= maxLength.Value #>)]
<#
}
if (!property.IsNullable)
{
#>
[Required]
<#
}
#>
public <#= code.Reference(property.ClrType) #> <#= property.Name #> { get; set; }
Template Variables
Templates have access to the EF Core model through the Model variable:
| Variable/Method | Description |
|---|---|
Model |
The full EF Core model |
Model.GetEntityTypes() |
All entity types in the model |
entityType.GetProperties() |
Properties of an entity |
entityType.GetNavigations() |
Navigation properties |
property.ClrType |
The CLR type of a property |
property.IsNullable |
Whether the property is nullable |
property.GetMaxLength() |
Maximum length constraint |
Troubleshooting
Templates not being used
Verify:
use-t4is set totrueinefcpt-config.json- Template files exist in the expected location
- Template directory is correctly resolved (check with
EfcptDumpResolvedInputs)
<PropertyGroup>
<EfcptLogVerbosity>detailed</EfcptLogVerbosity>
<EfcptDumpResolvedInputs>true</EfcptDumpResolvedInputs>
</PropertyGroup>
Template errors
Template compilation errors appear in the build output. Common issues:
- Syntax errors in T4 directives
- Missing assembly references
- Incorrect namespace references
Templates not updating
The fingerprint includes template files. If templates change, regeneration should occur automatically. If not:
# Force regeneration
rmdir /s /q obj\efcpt
dotnet build
Best Practices
- Start with defaults - Copy default templates and modify incrementally
- Version control templates - Keep templates in source control alongside your project
- Test changes - Build after each template change to catch errors early
- Use partial classes - Generate partial classes to separate generated and custom code
- Document customizations - Comment your template modifications for team awareness
Next Steps
- Configuration - Complete configuration reference
- Advanced Topics - Multi-project and complex scenarios
- API Reference - MSBuild task documentation