efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Compiled Model fails when discriminator uses non-primitive type (SmartEnum); cannot scaffold C# literals

Open firasdarwish opened this issue 1 month ago • 2 comments

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

  1. Use a discriminator property typed as a SmartEnum (or any custom type).
  2. Configure it with a value converter.
  3. Run dotnet ef dbcontext optimize.
  4. 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

firasdarwish avatar Nov 28 '25 20:11 firasdarwish

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 HasDefaultValue and HasDefaultValueSql
  • adding a ITypeMappingSourcePlugin for UserType
  • using UserType's Value (int) instead of Name (string)

firasdarwish avatar Nov 29 '25 09:11 firasdarwish

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;
    }
}

firasdarwish avatar Nov 29 '25 09:11 firasdarwish