I am trying to get EF Core 8.0.2's table-per-hierarchy working, with this simple program:
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.OfType<ConcreteItemA>().ToListAsync();
foreach (var item in items)
{
Console.WriteLine($"{item.Name} {item.Number:C2}");
}
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 class ConcreteItemB : BaseItem
{
public ConcreteItemB()
{
Type = BaseItemType.ConcreteB;
}
public decimal Amount { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet<BaseItem> Items => Set<BaseItem>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlite()
.LogTo(Console.WriteLine, LogLevel.Debug)
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
}
}
The entities are configured using these configuration classes:
public abstract class BaseItemTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : BaseItem
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.ToTable("Items")
.HasDiscriminator(b => b.Type)
.HasValue<ConcreteItemB>(BaseItemType.ConcreteB)
.HasValue<ConcreteItemA>(BaseItemType.ConcreteA)
.IsComplete();
builder.HasKey(b => b.Id);
builder.Property(b => b.Id).IsRequired().ValueGeneratedOnAdd();
builder.Property(b => b.Name).IsRequired();
ConfigureSubType(builder);
}
protected abstract void ConfigureSubType(EntityTypeBuilder<TEntity> builder);
}
public class ConcreteItemATypeConfiguration : BaseItemTypeConfiguration<ConcreteItemA>
{
protected override void ConfigureSubType(EntityTypeBuilder<ConcreteItemA> builder)
{
builder.Property(b => b.Number).HasDefaultValue(42);
builder.HasData(new ConcreteItemA { Name = "A", Number = 41 });
}
}
public class ConcreteItemBTypeConfiguration : BaseItemTypeConfiguration<ConcreteItemB>
{
protected override void ConfigureSubType(EntityTypeBuilder<ConcreteItemB> builder)
{
builder.Property(b => b.Amount).HasDefaultValue(42.0M);
builder.HasData(new ConcreteItemB { Name = "B", Amount = 41.0M });
}
}
When I run the app, it throws an InvalidOperationException with the following message:
Unable to create a 'DbContext' of type ''. The exception 'Cannot configure the discriminator value for entity type 'ConcreteItemB' because it doesn't derive from 'ConcreteItemA'.' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
Is it not possible to do all configuration through the IEntityTypeConfiguration<T>? Do I need to put some of it into the OnModelCreating, and if so, which parts?
This code won't work as you expect:
As a base class for the entity type config, that is going to get called for both concrete types, filling in for
TEntity. What you likely need to do is update the signature to:But you don't want it part of a base class entity type configuration, just an
IEntityTypeConfiguration<Item>, non-inherited.