Cannot map EF Core discriminator column to enum on existing column

35 Views Asked by At

I am attempting to get EF Core 8.0.2's type-per-hiearchy configuration working with an enum used for the discriminator. If you consider the following C# 12 code:

using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

using var context = new TestDbContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

await context.SaveChangesAsync();

var items = await context.Items.ToListAsync();
foreach (var item in items)
{
    Console.WriteLine(item.ToString());
}

public enum BaseItemType
{
    ConcreteA = 0,
    ConcreteB = 1
}

public abstract class BaseItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    public BaseItemType Type { get; set; }
}

public class ConcreteItemA : BaseItem
{
    public ConcreteItemA()
    {
        Type = BaseItemType.ConcreteA;
    }
    public int Number { get; set; }
    public override string ToString() => $"{Id,3}|{Name,10}|{Number,10:G2}";
}

public class ConcreteItemB : BaseItem
{
    public ConcreteItemB()
    {
        Type = BaseItemType.ConcreteB;
    }
    public decimal Amount { get; set; }
    public override string ToString() => $"{Id,3}|{Name,10}|{Amount,10:C2}";
}

public class TestDbContext : DbContext
{
    public DbSet<BaseItem> Items => Set<BaseItem>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlite("Data Source=items.db;Cache=Shared")
            .LogTo(Console.WriteLine, LogLevel.Debug)
            .EnableDetailedErrors();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
    }
}

public abstract class BaseItemTypeConfiguration : IEntityTypeConfiguration<BaseItem>
{
    public void Configure(EntityTypeBuilder<BaseItem> builder)
    {
        builder.ToTable("Items")
            .HasDiscriminator<BaseItemType>("Type")
            .HasValue<ConcreteItemB>(BaseItemType.ConcreteB)
            .HasValue<ConcreteItemA>(BaseItemType.ConcreteA)
            .IsComplete();

        builder.HasKey(b => b.Id);
        builder.Property(b => b.Id).IsRequired().ValueGeneratedOnAdd();

        Configure(builder);
    }
}

public class ConcreteItemATypeConfiguration : IEntityTypeConfiguration<ConcreteItemA>
{
    public void Configure(EntityTypeBuilder<ConcreteItemA> builder)
    {
        builder.Property(b => b.Number).HasDefaultValue(42);
        builder.HasData(new ConcreteItemA { Id = 1, Name = "A", Number = 41 });
    }
}

public class ConcreteItemBTypeConfiguration : IEntityTypeConfiguration<ConcreteItemB>
{
    public void Configure(EntityTypeBuilder<ConcreteItemB> builder)
    {
        builder.Property(b => b.Amount).HasDefaultValue(42.0M);
        builder.HasData(new ConcreteItemB { Id = 2, Name = "B", Amount = 41.0M });
    }
}

The code generates the expected output; however, the database schema that is created does not match my expectations. Instead of using the Type column, it adds a shadow property named Discriminator of type text, and populates it with the string values of the enum, in addition to populating the type field with the integer values of the enum. I can see the following message in the logs:

The property 'BaseItem.Discriminator' was created in shadow state because there are no eligible CLR members with a matching name.

So EF Core generates the following SQL to create and populate the table:

CREATE TABLE "Items" (
  "Id" INTEGER NOT NULL CONSTRAINT "PK_Items" PRIMARY KEY AUTOINCREMENT,
  "Name" TEXT NOT NULL,
  "Type" INTEGER NOT NULL,
  "Discriminator" TEXT NOT NULL,
  "Number" INTEGER NULL DEFAULT 42,
  "Amount" TEXT NULL DEFAULT '42.0'
);
INSERT INTO "Items" ("Id", "Discriminator", "Name", "Number", "Type")
VALUES (1, 'ConcreteItemA', 'A', 41, 0);
SELECT changes();
INSERT INTO "Items" ("Id", "Amount", "Discriminator", "Name", "Type")
VALUES (2, '41.0', 'ConcreteItemB', 'B', 1);
SELECT changes();

and the following query to retrieve the values:

SELECT "i"."Id", "i"."Discriminator", "i"."Name", "i"."Type", "i"."Number", "i"."Amount"
FROM "Items" AS "i"

How do I stop EF Core from adding the shadow Discriminator property to the table? I tried using the following in BaseItemTypeConfiguration without success:

builder.ToTable("Items")
    .HasDiscriminator(b => b.Type)
    .HasValue<ConcreteItemB>(BaseItemType.ConcreteB)
    .HasValue<ConcreteItemA>(BaseItemType.ConcreteA)
    .IsComplete();
0

There are 0 best solutions below