Scaffold not handling startup assembly, therefore not discovering custom DesignTimeService
See original GitHub issueWe’re having a custom IDesignTimeServices
implementation in our EF core project to perform some additional customizations when scaffolding from the database. This customization is changing the handlebar behavior by excluding some additional tables and changing property types.
When scaffolding from command line with dotnet scaffold
the design time services are applied correctly.
When using EF Core Power Tools “Reverse Engineer”, the services are not applied. Assumption what’s causing this issue is stated below in “Analysis”.
Steps to reproduce
- Create blank solution.
- Add empty database project and empty C# project to scaffold context and entities into, including NuGet packages for EF Core and handlebars.
- Add table
dbo.User
withUserId INT NOT NULL PRIMARY KEY
column to the database project. - Add custom design time services to C# project.
- Configure handlebar transformers in custom design time services:
public class ScaffoldingDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
services.AddHandlebarsTransformers(propertyTransformer: p =>
{
// Map to strong types
var nullable = p.PropertyType.Contains("System.Nullable") || p.PropertyType.Contains("?");
if (p.PropertyName.Contains("UserId"))
return new EntityPropertyInfo(CreateNewPropertyType<UNITS.Shared.StrongTypes.InternalUserId>(nullable), p.PropertyName);
});
}
private static string CreateNewPropertyType(string propertyType) => propertyType == "string" ? "string?" : propertyType;
private static string CreateNewPropertyType<TType>(bool nullable)
{
var fullName = typeof(TType).FullName;
if (fullName == null)
throw new InvalidOperationException();
return nullable ? $"{fullName}?" : fullName;
}
- Scaffold the database project into the C# project with handlebars using the “Reverse Engineer” command of EF Core Power Tools.
- Generated entity for the
User
table will have a propertyint UserId { get; set; }
(this is wrong, design time service was not applied). - Scaffold the database project into the C# project with handlebars using the command line dbcontext scaffold.
- Generated entity for the
User
table will have a propertyUNITS.Shared.StrongTypes.InternalUserId UserId { get; set; }
(this is correct, design time service was applied).
Further technical details
EF Core Power Tools version: 2.4.51.0 Database engine: SQL Server / Scaffold from DACPAC Visual Studio version: Visual Studio 2019 16.6.2
Analysis
I had a look at the verbose command line logs of the dbcontext scaffold
command and at the source code of the EF Core scaffold pipeline.
The verbose output points out that the custom design time services class is found:
Using assembly 'UNITS.Server.Database'.
Using startup assembly 'UNITS.Server.Database'.
Using application base 'S:\herdo-opensource\HoU UNITS\src\Server.Database\bin\Debug\netcoreapp3.1'.
Using working directory 'S:\herdo-opensource\HoU UNITS\src\Server.Database'.
Using root namespace 'UNITS.Server.Database'.
Using project directory 'S:\herdo-opensource\HoU UNITS\src\Server.Database\'.
Finding design-time services for provider 'Microsoft.EntityFrameworkCore.SqlServer'...
Using design-time services from provider 'Microsoft.EntityFrameworkCore.SqlServer'.
Finding design-time services referenced by assembly 'UNITS.Server.Database'.
No referenced design-time services were found.
---> These two lines are important:
Finding IDesignTimeServices implementations in assembly 'UNITS.Server.Database'...
Using design-time services from class 'ScaffoldingDesignTimeServices'.
Looking at the source code of the pipeline in the Microsoft.EntityFrameworkCore.Design.Internal.DesignTimeServicesBuilder
class the IDesignTimeServices
implementations are searched for in three steps:
ConfigureProviderServices
--> which would be the EF Core Power Tools DACPAC provider in the scenario above.ConfigureReferencedServices
--> probably none?ConfigureUserServices
--> this is not working with EF Core Power Tools
ConfigureUserServices
however is the important part. This method is loading the IDesignTimeServices
implementation from the startup project:
private void ConfigureUserServices(IServiceCollection services)
{
_reporter.WriteVerbose(DesignStrings.FindingDesignTimeServices(_startupAssembly.GetName().Name));
var designTimeServicesType = _startupAssembly.GetLoadableDefinedTypes()
.Where(t => typeof(IDesignTimeServices).IsAssignableFrom(t)).Select(t => t.AsType())
.FirstOrDefault();
if (designTimeServicesType == null)
{
_reporter.WriteVerbose(DesignStrings.NoDesignTimeServices);
return;
}
_reporter.WriteVerbose(DesignStrings.UsingDesignTimeServices(designTimeServicesType.ShortDisplayName()));
ConfigureDesignTimeServices(designTimeServicesType, services);
}
Therefore, I assume there’s an issue that EF Core Power Tools is not passing the startup assembly correctly to the scaffold pipeline.
Issue Analytics
- State:
- Created 3 years ago
- Comments:10 (10 by maintainers)
Top GitHub Comments
This can be done with the command line tooling, not something I plan to implement.
This is not something I plan to support, too many pits of failure.