Compiled Model fails when discriminator uses non-primitive type (SmartEnum); cannot scaffold C# literals
Bug description
When generating a compiled model using:
dotnet ef dbcontext optimize
the command fails if the discriminator property is a non-primitive type such as Ardalis.SmartEnum.
Expected behavior The compiled model generator should honor the value converter for the discriminator and emit a string literal (the provider-side type), or EF Core should otherwise support discriminator values that rely on custom value conversions.
Actual behavior
The generator attempts to build C# literals for the CLR type (UserType), fails because it is not a primitive/literal-friendly type, and exits with the error above.
**Note **: It works perfectly fine in normal situations (ie. not using compiled models).
Repro steps
- Use a discriminator property typed as a SmartEnum (or any custom type).
- Configure it with a value converter.
- Run
dotnet ef dbcontext optimize. - Observe failure during model scaffolding.
Example project: https://github.com/firasdarwish/TestCompiledComplexDiscriminator
Your code
Entity configuration:
builder.Property(p => p.UserType)
.HasConversion(s => s.Name, s => UserType.FromName(s, true))
.HasMaxLength(20);
builder.HasDiscriminator(p => p.UserType)
.HasValue<NormalUser>(UserType.Normal)
.HasValue<AdminUser>(UserType.Admin)
.IsComplete();
SmartEnum definition:
public sealed class UserType : SmartEnum<UserType>
{
public static readonly UserType Normal = new("Normal", 1);
public static readonly UserType Admin = new("Admin", 2);
public UserType(string name, int value) : base(name, value) { }
}
POCOs:
public abstract class BaseUser
{
public int Id { get; set; }
public abstract UserType UserType { get; protected set; }
public string Name { get; set; } = null!;
}
public sealed class NormalUser : BaseUser
{
public override UserType UserType { get; protected set; } = UserType.Normal;
public string? Email { get; set; }
}
public sealed class AdminUser : BaseUser
{
public override UserType UserType { get; protected set; } = UserType.Admin;
public string? PhoneNumber { get; set; }
}
Stack traces
dotnet ef dbcontext optimize
Build started...
Build succeeded.
System.InvalidOperationException: Cannot scaffold C# literals of type 'TestCompiledComplexDiscriminator.Models.UserType'. The provider should implement CoreTypeMapping.GenerateCodeLiteral to support using it at design time.
at Microsoft.EntityFrameworkCore.Design.Internal.CSharpHelper.UnknownLiteral(Object value)
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters)
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.CreateEntityType(IEntityType entityType, String namespace, IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, SortedSet`1 namespaces, Dictionary`2 configurationClassNames, Dictionary`2 memberAccessReplacements, Boolean nullable, Boolean nativeAot)
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.GenerateEntityType(IEntityType entityType, String namespace, Dictionary`2 entityClassNames, Dictionary`2 memberAccessReplacements, Boolean nullable, Boolean nativeAot)
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGenerator.GenerateModel(IModel model, CompiledModelCodeGenerationOptions options)
at Microsoft.EntityFrameworkCore.Scaffolding.Internal.CompiledModelScaffolder.ScaffoldModel(IModel model, String outputDir, CompiledModelCodeGenerationOptions options)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.ScaffoldCompiledModel(String outputDir, String modelNamespace, DbContext context, String suffix, Boolean nativeAot, IServiceProvider services, ISet`1 generatedFileNames)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.Optimize(String outputDir, String modelNamespace, String suffix, Boolean scaffoldModel, Boolean precompileQueries, DbContext context, Boolean optimizeAllInAssembly, Boolean nativeAot, List`1 generatedFiles, HashSet`1 generatedFileNames)
at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.Optimize(String outputDir, String modelNamespace, String contextTypeName, String suffix, Boolean scaffoldModel, Boolean precompileQueries, Boolean nativeAot)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OptimizeContextImpl(String outputDir, String modelNamespace, String contextType, String suffix, Boolean scaffoldModel, Boolean precompileQueries, Boolean nativeAot)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OptimizeContext.<>c__DisplayClass0_0.<.ctor>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Verbose output
Cannot scaffold C# literals of type 'TestCompiledComplexDiscriminator.Models.UserType'.
The provider should implement CoreTypeMapping.GenerateCodeLiteral to support using it at design time.
EF Core version
10.0.0 + 9.0.10
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.NET 10.0.0 + .NET 9.0.10
Operating system
Windows 11
IDE
JetBrains Rider
i've tried the following but it still fails with the same error message:
- global value converter for
UserType - adding value comparer for
UserType - using
HasDefaultValueandHasDefaultValueSql - adding a
ITypeMappingSourcePluginforUserType - using
UserType's Value (int) instead of Name (string)
it also fails with the same error when using other complex types (other than Ardalis.SmartEnum) for the discriminator property
public sealed class CustomType
{
public int Value { get; }
public CustomType(int value)
{
Value = value;
}
public static readonly CustomType Normal = new CustomType(1);
public static readonly CustomType Admin = new CustomType(2);
public override bool Equals(object? obj)
{
return obj is CustomType other && Value == other.Value;
}
private bool Equals(CustomType other)
{
return Value == other.Value;
}
public override int GetHashCode()
{
return Value;
}
}