I’m a huge fan of T4–the underutilized templating engine that ships as part of Visual Studio.

Entity Framework 6 allows you to customize the code generated for a model using T4 templates, and I’ve been trying to bring bring this functionality into EF Core since the beginning.

I finally got around to putting together a sample–in the bricelam/EFCore.TextTemplating repo–that demonstrates how to use T4 to customize the code scaffolded by EF Core. Technically, this has been possible since EF Core 2.1.

Here’s an example of a T4 template that generates a DbContext class:

<#@ template language="C#" #>
<#@ parameter name="Model" type="Microsoft.EntityFrameworkCore.Metadata.IModel" #>
<#@ parameter name="ModelNamespace" type="System.String" #>
<#@ parameter name="Namespace" type="System.String" #>
<#@ parameter name="ContextName" type="System.String" #>
<#@ parameter name="ConnectionString" type="System.String" #>
<#@ parameter name="Code" type="Microsoft.EntityFrameworkCore.Design.ICSharpHelper" #>
<#@ parameter name="ProviderCode" type="Microsoft.EntityFrameworkCore.Scaffolding.IProviderConfigurationCodeGenerator" #>
using Microsoft.EntityFrameworkCore;
using <#= ModelNamespace #>;

namespace <#= Namespace #>
{
    public partial class <#= ContextName #> : DbContext
    {
<#
    foreach (var entityType in Model.GetEntityTypes())
    {
#>
        public DbSet<<#= entityType.Name #>> <#= entityType["Scaffolding:DbSetName"] #> { get; set; }
<#
    }
#>

        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options<#= Code.Fragment(ProviderCode.GenerateUseProvider(ConnectionString)) #>;

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
<#
    foreach (var entityType in Model.GetEntityTypes())
    {
#>
            modelBuilder.ApplyConfiguration(new <#= entityType.Name #>Configuration());
<#
    }
#>
        }
    }
}

Warning! Staring directly at T4 templates without syntax highlighting may hurt your eyes. I strongly recommend using the Devart T4 Editor for Visual Studio or T4 Support by Zachary Becknell for VS Code.

I took this opportunity to fix a few of the pet peeves I have with EF Core’s built-in scaffolding: First, it only generates validation attributes since these are actually used by other technologies like ASP.NET Core. Generating data annotations only used by EF always seemed a bit silly to me. Second, it only generates the parts of the model that affect EF’s behavior. Things like sequences, foreign key constraint names, and non-unique indexes just clutter the model in my opinion. (Note, these things are useful in Migrations, just not in a database-first workflow.) Finally, it generates configuration classes to keep OnModelCreating from becoming exceedingly large and unmaintainable. But please remember, these templates are merely a starting point. Feel free to tweak them to your heart’s content.

To get started, clone the repository and copy the EFCore.TextTemplating project into your solution. You’ll also need to add an assembly-level attribute to your app:

[assembly: DesignTimeServicesReference(
    "EFCore.TextTemplating.DesignTimeServices, EFCore.TextTemplating")]

After doing this, the templates should now be used whenever you reverse engineer a model from the database:

dotnet ef dbcontext scaffold "Data Source=Chinook.db" Microsoft.EntityFrameworkCore.Sqlite

If you’re not using Visual Studio, you’ll need to use dotnet-ef after editing the template files to re-generate the code-behind files:

dotnet tool install -g dotnet-t4

t4 MyDbContextGenerator.tt -c MyDbContextGenerator -o MyDbContextGenerator.cs
t4 MyEntityTypeConfigurationGenerator.tt -c MyEntityTypeConfigurationGenerator -o MyEntityTypeConfigurationGenerator.cs
t4 MyEntityTypeGenerator.tt -c MyEntityTypeGenerator -o MyEntityTypeGenerator.cs

And that’s it, you now have full control over the scaffolded code. You can do things like define how table and column names map to class and property names, add a dictionary to define navigation property names based on a foreign key name, or finally fix all those analyzer issues we that we keep closing–it’s just C#!