Demos for July 2020 EF Core Community Standup.
Prerequisites:
- NorthwindSlim sample database.
- Create NorthwindSlim database.
- Run NorthwindSlim.sql script.
- EF Core Power Tools.
References:
Note: When using Visual Studio for simple use cases, the EF Core Power Tools offers a graphical user interface and the ability to directly target a .NET Standard library.
For advanced features, such as Handlebars helpers and transformers, custom template data, nullable reference types and schema folders, it is necessary to use the EF Core CLI, which requires a "tooling" project that is a .NET Core library. You also need to add a
ScaffoldingDesignTimeServices
class to the tooling project.
Note: Handlebars scaffolding includes additional features not shown in this demo. For details refer to the project ReadMe.
- Add Handlebars block helpers.
- Exclude tables from code generation.
- Add custom template data for use in Handlebars templates.
- Enable schema folders to place generated entities in folders according to database schemas.
- Translate table and column descriptions to XML comments.
- Embed Handlebars templates into a separate assembly.
Many of these features were contributed by members of the community. To submit an issue or pull request, please refer to the project Contributing Guidelines.
Note: The easiest way to reverse engineer entities from an existing database is to use a Visual Studio extension called the Entity Framework Code Power Tools, which allow you to customize generated code using Handlebars templates.
- Create a .NET Standard library project with a .Entities suffix.
- Because EF Core Power Tools is a Visual Studio extension, you can use it on a .NET Standard Library.
- Add a Data Connection to the Server Explorer.
- Server name:
(localdb)\MsSqlLocalDb
- Select
NorthwindSlim
database.
- Server name:
- Right-click on ScaffoldingHandlebars.Entities project in the Solution Explorer, select EF Core Power Tools, Reverse Engineer.
- Modify Handlebars templates to control code generation.
- Modify the CSharpEntityType/Class.hbs file to derive from
EntityBase
. - Add
EntityBase
class to the project. - Modify CSharpEntityType/Partials/Properties.hbs to change
ICollection
toList
. - Update CSharpEntityType/Partials/Constructor.hbs change
HashSet
toList
. - Add a parameterless constructor to CSharpDbContext/Partials/Constructor.hbs.
- Rerun the EF Core Power Tools.
- Modify the CSharpEntityType/Class.hbs file to derive from
Note: The EF Core Power Tools also allow you to reverse engineer TypeScript entities from a database and customize them using Handlebars templates.
-
Create a .NET Standard library project with a .TypeScript suffix.
-
Right-click on ScaffoldingHandlebars.Entities project in the Solution Explorer, select EF Core Power Tools, Reverse Engineer.
- Choose the NorthwindSlim data connection.
- Select all tables.
- What to generate: Entity Types only
- Check: Customize code using Handlebars templates.
- Select TypeScript as the language.
Note: First we will create a separate .NET Standard .Data project for the generated
DbContext
class, so that the .Entities project will not require any dependency on EF Core and can be references by interfaces that are ignorant of persistence concerns.
Because the EF Core CLI requires the .NET Core runtime, it is helpful to create a .Tooling .NET Core Library project, which will generate the context and entities in the .NET Standard .Data and .Entities projects.
- Install EF Core Global Tool
dotnet tool install --global dotnet-ef
- You may need to update the tools to the latest available version.
dotnet tool update --global dotnet-ef
- Delete the CodeTemplates, Contexts and Models folders from the .Entities project.
- Create a .NET Standard project with a .Data suffix.
- This is where the
DbContext
class will be generated. - Reference the .Entities project from the .Data project.
- Add EF Core SQL Server package.
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
- This is where the
- Create a .NET Core library project with a .Tooling suffix.
- The tools need the .NET Core runtime to execute, so a .NET Standard project cannot be used by the tooling.
- Reference the .Entities project from the .Tooling project.
- Add EF Core Design, Scaffolding.Handlebars packages.
dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package EntityFrameworkCore.Scaffolding.Handlebars
- Add a
ScaffoldingDesignTimeServices
class that implementsIDesignTimeServices
.public class ScaffoldingDesignTimeServices : IDesignTimeServices { public void ConfigureDesignTimeServices(IServiceCollection services) { services.AddHandlebarsScaffolding(); } }
- Run the
dotnet ef dbcontext scaffold
command.- Make sure you are in the .Tooling project directory.
- Specify connection string, EF Core SQL Server provider, and target project.
DbContext
can be generated in the .Data project by specifying the--context-dir
argument.- Entities can be generated in a separate project by specifying the
--project
argument and specifying the .Entities project.
dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=NorthwindSlim; Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -o Models -c NorthwindSlimContext --context-dir ../ScaffoldingHandlebars.Data/Contexts --project ../ScaffoldingHandlebars.Entities --force
Note: Handlebars scaffolding supports generation of entities with nullable reference type semantics.
- Upgrade both the .Data and .Entities projects to .NET Standard version 2.1.
- Either edit the .csproj files directory or select Target Framework on the Application tab of the project properties page in Visual Studio.
- Enable Nullable for both the .Data and .Entities projects.
- Either edit the .csproj files directory or select Target Framework on the Build tab of the project properties page in Visual Studio.
- Update
services.AddHandlebarsScaffolding
inScaffoldingDesignTimeServices
in the .Tooling project to enable nullable reference types.services.AddHandlebarsScaffolding(options => options.EnableNullableReferenceTypes = true );
- Run the
dotnet ef dbcontext scaffold
command.- Use the same command as shown above.
- Notice that optional properties for reference types are set to nullable by appending a
?
to the type. - Notice that required properties are set using the null forgiving operator, which eliminates compiler warnings.
Note: Handlebars Helpers allow you to inject custom text into generated entities using a C# tuple with a
context
parameter that includes template data.
- Update
ScaffoldingDesignTimeServices.ConfigureDesignTimeServices
in the .Tooling project by adding a call toservices.AddHandlebarsHelpers
.services.AddHandlebarsHelpers(("my-helper", (writer, context, parameters) => writer.Write($"// My Handlebars Helper: {context["class"]}") ));
- Update CSharpEntityType/Class.hbs to add
{{my-helper}}
- Run the
dotnet ef dbcontext scaffold
command from above. - To inspect the
context
variable, add a breakpoint inside the lambda statement, then add the following line of code inConfigureDesignTimeServices
.Debugger.Launch();
- When the scaffolding command is run you will be prompted to select an instance of Visual Studio for attaching the JIT Debugger.
- When the breakpoint is hit you can inspect the content of the
context
parameter.
- When the breakpoint is hit you can inspect the content of the
Note: Handlebars Transformers are a lightweight mechanism for transforming entities and their members. For example, this can allow you to change a property from a
string
to anenum
.
In this example we will change the
Country
property ofEmployee
fromstring
to an enum calledCountry
with values that match data in theEmployee
and tables.
- Add a
Country
enum to the .Entities project.public enum Country { UK = 1, USA = 2 }
- Add a call to
services.AddHandlebarsTransformers
toConfigureDesignTimeServices
, in which you pass a property transformer.services.AddHandlebarsTransformers(propertyTransformer: e => e.PropertyName == nameof(Country) ? new EntityPropertyInfo(nameof(Country), nameof(Country), e.PropertyIsNullable) : new EntityPropertyInfo(e.PropertyType, e.PropertyName, e.PropertyIsNullable) );
- To test this add a .NET Core console app with a .ConsoleClient suffix.
- Add Microsoft.EntityFrameworkCore.SqlServer package.
- Reference both .Data and .Entities projects.
- Add code to
Program.Main
for displaying name, city and country forEmployees
.
using (var context = new NorthwindSlimContext()) { var employees = context.Employee.Select(e => new { Name = $"{e.FirstName} {e.LastName}", e.City, e.Country }); foreach (var e in employees) { Console.WriteLine($"{e.Name} {e.City} {e.Country} "); } }
- Press Ctrl+F5 to run the console client.
- Notice the
InvalidCastException
convertingstring
toint
.
- To fix the error add a NorthwindSlimContextPartial.cs file with a partial
NorthwindSlimContext
class that implements the partialOnModelCreatingPartial
method.- Specify
HasConversion
for theEmployee.Country
property, converting theCountry
property betweenstring
and theCountry
enum.
namespace ScaffoldingHandlebars.Entities { public partial class NorthwindSlimContext { partial void OnModelCreatingPartial(ModelBuilder modelBuilder) { modelBuilder.Entity<Employee>() .Property(e => e.Country) .HasConversion( v => v.ToString(), v => (Country)Enum.Parse(typeof(Country), v)); } } }
- Specify
- Re-run the console client. The exception will no longer appear.
Note: To take full control of context and entity generation, you can extend
HbsCSharpDbContextGenerator
andHbsCSharpEntityTypeGenerator
, overriding select virtual methods. Then register your custom generators inScaffoldingDesignTimeServices.ConfigureDesignTimeServices
.
For example, you may want to add
property-isprimarykey
to the template data in order to insert some code or a comment.
- Add a
MyHbsCSharpEntityTypeGenerator
to the .Tooling project.- Extend
HbsCSharpEntityTypeGenerator
. - Override
GenerateProperties
. - Copy code from the base
GenerateProperties
method. - Add code that inserts
property-isprimarykey
into the template data.
protected override void GenerateProperties(IEntityType entityType) { var properties = new List<Dictionary<string, object>>(); foreach (var property in entityType.GetProperties().OrderBy(p => p.GetColumnOrdinal())) { // Code elided for clarity properties.Add(new Dictionary<string, object> { { "property-type", propertyType }, { "property-name", property.Name }, { "property-annotations", PropertyAnnotationsData }, { "property-comment", property.GetComment() }, { "property-isnullable", property.IsNullable }, { "nullable-reference-types", _options?.Value?.EnableNullableReferenceTypes == true }, // Add new item to template data { "property-isprimarykey", property.IsPrimaryKey() } }); } var transformedProperties = EntityTypeTransformationService.TransformProperties(properties); // Add to transformed properties for (int i = 0; i < transformedProperties.Count ; i++) { transformedProperties[i].Add("property-isprimarykey", properties[i]["property-isprimarykey"]); } TemplateData.Add("properties", transformedProperties); }
- Extend
- Register
MyHbsCSharpEntityTypeGenerator
inScaffoldingDesignTimeServices.ConfigureDesignTimeServices
.services.AddSingleton<ICSharpEntityTypeGenerator, MyHbsCSharpEntityTypeGenerator>();
- Update CSharpEntityType/Partials/Properties.hbs to add
property-isprimarykey
. - Run the
dotnet ef dbcontext scaffold
command from above.