Customize EF Core Scaffolding with Handlebars Templates

With the release of Entity Framework Core 2.1 we finally have a version of EF Core that is ready for prime time.  EF Core is a complete re-write of its predecessor Entity Framework 6, which has been married to the full Windows .NET Framework since it was released in 2008 as part of .NET 3 SP1. EF Core, on the other hand, was designed using modern programming concepts such as greater modularity via dependency injection and can run on non-Windows platforms that provide more options for cloud-based deployments that are serverless or use containers.

But writing a new data access stack from scratch required numerous tradeoffs. On the plus side, the EF Core team added long awaited but never realized features, such as mixed client/server query evaluation and composable queries that include raw SQL.  On the downside, they had to cut features considered essential to some teams, such as table-per-type inheritance modeling and support for mapping stored procedures to CUD operations. On balance, however, as of version 2.1 EF Core has been able to achieve much better parity with EF 6 by including previous show-stoppers such as GroupBy query translation and support for System.Transactions. (See this article for a feature-by-feature comparison between EF Core and EF 6.)

While the EF Core runtime has matured to the point where it can be considered a viable option for enterprise applications, the tooling has lagged behind.  For example, there is still no Visual Studio wizard for reverse engineering an existing database to model and context classes.  For that you’ll need to resort to the command line.

Note: Erik Ejlskov Jensen has authored the EF Core Power Tools, which provide a UI for reverse engineering classes from an existing database and use my Handlebars plugin under the covers. They also allow you to perform migrations and visualize your DbContext in various ways.

While that doesn’t present too much difficulty, there has not been a way to customize classes generated by the EF Core tooling.

That is, until now.

I have authored a plug-in (EntityFrameworkCore.Scaffolding.Handlebars) that allows you to use Handlebars templates to customize classes that are reverse engineered from an existing database using the dotnet ef dbcontext scaffold command.  To use the plugin simply add my extension NuGet package to your EF Core Class Library project, then add a class that implements IDesignTimeServices by calling the AddHandlebarsScaffolding extension method that hangs off IServiceCollection.


public class ScaffoldingDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
var options = ReverseEngineerOptions.DbContextAndEntities;
services.AddHandlebarsScaffolding(options);
}
}

Next, open a command prompt at the project level and use the .NET Core CLI to reverse engineer a context and models from an existing database.  For example, if you have downloaded scripts to create the NorthwindSlim sample database for SQL Server LocalDb, you can run the following command:


dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=NorthwindSlim; Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -o Models -c NorthwindSlimContext -f –context-dir Contexts

The first time you run the command you’ll see a CodeTemplates folder magically appear in your project.

hbs-scaffolding-sample

Handlebars Templates

There you’ll find Handlebars templates for context and entity classes which you can customize to your heart’s content.


{{> dbimports}}
namespace {{namespace}}
{
public partial class {{class}} : DbContext
{
{{> dbsets}}
{{#if entity-type-errors}}
{{#each entity-type-errors}}
{{spaces 8}}{{{entity-type-error}}}
{{/each}}
{{/if}}
{{{on-configuring}}}
{{{on-model-creating}}}
}
}

view raw

DbContext.hbs

hosted with ❤ by GitHub

Notice there are also partial templates that you can also customize.  (One reason you might want to customize generated entity classes would be to implement an interface or extend a base class.)  The next time you run the scaffolding command, you’ll see your changes reflected in the generated classes.

Lastly, my plug-in allows you to register Handlebars helpers for further customizing output based on runtime conditions. Simple pass one or more named tuples to the AddHandlebarsScaffolding extension method.


public class ScaffoldingDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
// Generate both context and entitites
var options = ReverseEngineerOptions.DbContextAndEntities;
// Register Handlebars helper
var myHelper = (helperName: "my-helper", helperFunction: (Action<TextWriter, object, object[]>) MyHbsHelper);
// Add Handlebars scaffolding templates
services.AddHandlebarsScaffolding(options, myHelper);
}
// Sample Handlebars helper
void MyHbsHelper(TextWriter writer, object context, object[] parameters)
{
writer.Write("// My Handlebars Helper");
}
}

Then insert the helper into your Handlebars template as in the following example.


public partial class {{class}} {{my-helper}}
{
{{{> constructor}}}
{{> properties}}
}

This will result in the helper rendering your desired content, as shown in the following example.


public partial class Category // My Handlebars Helper
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<Product> Product { get; set; }
}

view raw

helper-class.cs

hosted with ❤ by GitHub

For further information and detailed instructions please visit the ReadMe on the project repo. Enjoy!

About Tony Sneed

Sr. Software Solutions Architect, Hilti Global Application Software
This entry was posted in Technical and tagged , . Bookmark the permalink.

14 Responses to Customize EF Core Scaffolding with Handlebars Templates

  1. Jens says:

    This makes me wonder, how many versions of EF are there really ? And how many times has it been rewritten ?
    And all the while we had L2S which worked, still works, and we still use to our satisfaction. The only problem with it was that it was written by the wrong team. The guys at MS really should get their priorities straight.

  2. pointerstop says:

    Beautiful. I’m using it to add a [Display(Name=…)] attribute to my models, and I’m sure I’m not doing it _right_, but it does the job!

  3. This technique has been awesome so far but I’ve run into a bit of trouble. I am trying to register a helper function. I am getting a compile error. No overload for method ‘AddHandlebarsScaffolding’ takes 2 arguments. I have confirmed I am using the latest version. Any ideas?

    • Tony Sneed says:

      Are you using the EF Core CLI – and not the EF Core Power Tools? The helper functions are only available from the command line. See the project readme for more info.

  4. Mark Moore says:

    We are trying to use this is in a brand new winform project. Every time we run the scaffolding in the CLI we get an error about a circular dependency in the target dependency involving target “GetEFProjectMetadata”. We can’t even get into creating Helpers in order to set up our models the way we need them. Any help would be greatly apricated.

  5. Roman says:

    Great thanks for the contribution, especially considering lack of proper reverse engireenig customisation. I liked
    their ICSharpEntityTypeGenerator approach, until I figured out, that meanwhile, you could only rewrite result string, and they dont provide full control on process. (I could implement interface myself, but its excessive for my case).

    Basically I need just to remove some properties from entity on generaition time, since I moved them to base class (like createTime, UpdateTime etc).

    Now, from your solution HandlebarsHelpers are of type Action and I could really only writing code to pass string.
    I had few workarounds in my head, spoiler: none of them worked.

    Lets say I have helperName: “property-fits”,

    First if helper could ve return some type, I would ve just write
    {{#if property-fits property-name}} and would ve skip properties

    Second which may work is to pass template string into helper and simple doesnt call method write inside method. Like this
    {{property-fits –some escape character here– {{spaces 8}}public {{property-type}} {{property-name}} { get; set; }}}
    But I really don’t get the right syntax.

    I just started to investigate your source code to realize how it works. And if Im not mistaken, Its not the straight use of Handlebar template? At the end you still parse it inside ICSharpEntityTypeGenerator?

    So in addition to my long post, few general questions.
    How much is your solution customizable? How much percent is it Handlebar?=) like how can I extend it or use js to customize it somewhere (adding for example continue or break into {{#each}}).
    Im kinda new to this topic, used T4 before.

    P.S. sorry for grammar, feel free to ask if something not clear #notMyNativeLanguage =)

    • itfake says:

      I have found solution for this ….

      You need to add helper like this

      void MarkNotBaseProperties(TextWriter writer, object context, object[] parameters)
      {

      var values = (Dictionary)context;
      var baseProperties = new List<Dictionary>();
      var properties = (List<Dictionary>) values[“properties”];
      foreach (var item in properties)
      {
      if (item.ContainsKey(“property-name”) && item[“property-name”].ToString() == “Id”)
      {
      baseProperties.Add(item);
      item.Add(“is-not-base-property”, false);
      }
      else
      {
      item.Add(“is-not-base-property”, true);
      }
      }

      values[“properties”] = properties;
      }

      It will adjust every property with adding is-not-base-property values as false/true.

      Later in properties.hbs its used as following

      {{#if is-not-base-property }}
      {{spaces 8}}public {{property-type}} {{property-name}} { get; set; }
      {{/if}}

      Thats it

  6. Bret Cline says:

    Great info! One thing I’m stuck on. I’m trying to add another partial to call from the Class.hbs and it doesn’t seem to want to recognize it. The error is as follows where navproperties is my new partial.
    I’m calling it in the Class.hbs like this:

    {{{> constructor}}}
    {{{> properties}}}
    {{{> navproperties}}}

    Thanks for any help you can provide.

    Exception:

    HandlebarsDotNet.HandlebarsRuntimeException: Referenced partial name navproperties could not be resolved
    at HandlebarsDotNet.Compiler.PartialBinder.HandleFailedInvocation(String partialName)
    at lambda_method(Closure , TextWriter , Object )
    at HandlebarsDotNet.Handlebars.HandlebarsEnvironment.c__DisplayClass7_0.b__0(Object context)
    at EntityFrameworkCore.Scaffolding.Handlebars.HbsEntityTypeTemplateService.GenerateEntityType(Object data)
    at EntityFrameworkCore.Scaffolding.Handlebars.HbsCSharpEntityTypeGenerator.WriteCode(IEntityType entityType, String namespace, Boolean useDataAnnotations)
    at EntityFrameworkCore.Scaffolding.Handlebars.HbsCSharpModelGenerator.GenerateModel(IModel model, String namespace, String contextDir, String contextName, String connectionString, ModelCodeGenerationOptions options)
    at Microsoft.EntityFrameworkCore.Scaffolding.Internal.ReverseEngineerScaffolder.ScaffoldModel(String connectionString, IEnumerable`1 tables, IEnumerable`1 schemas, String namespace, String language, String contextDir, String contextName, ModelReverseEngineerOptions modelOptions, ModelCodeGenerationOptions codeOptions)
    at ReverseEngineer20.EfCoreReverseEngineer.GenerateFiles(ReverseEngineerOptions reverseEngineerOptions)
    at EFCorePowerTools.Handlers.ReverseEngineerHandler.d__2.MoveNext() in C:\projects\efcorepowertools\src\GUI\EFCorePowerTools\Handlers\ReverseEngineerHandler.cs:line 214

  7. gdycus says:

    Thanks so much for your contribution with handlebars! I’m working to migrate a large EF project to EF Core. Not sure why, but EF Core adds the word “Navigation” to each FK property. I’m hoping to use handlebars to strip out the word “Navigation” from the property names. Is it possible to that in the following code from DbSets.hbs?

    {{#each dbsets}}
    {{spaces 8}}public virtual DbSet {{set-property-name}} { get; set; }
    {{/each}}

  8. Tim Wallace says:

    I installed the EF Core Power Tools extension and really appreciate the power that your handlebars implementation provides. The one thing I need to do that I have not yet found documentation for is the ability to conditionally add an interface to the class. I know how to add one unconditionally, but there are some tables in my schema for which multiple interfaces need to be declared while others have a “base” interface. Can you point me toward documentation that would clear this up for me?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.