EF Core Fluent API Configuration Prevents TPC Inhe

2019-08-14 07:55发布

I have models which inherit from each other, but I am struggling to get the fluent api configuration to behave as I want it to. Let's say I have a base class which defines some core properties

public class Entity
{
   public int Id { get; set; }
   public string Title { get; set };
}

And a subclass for Book

public class Book : Entity
{
    public int Edition { get; set; }
}

This way I can have books, magazines, pamphlets, comics, speeches etc, all inheriting from my Entity, and not have to define the relationship on every class.

Now I add my DbSets to the DbContext like this

public class ApplicationDbContext : DbContext
{
   public virtual DbSet<Book> Books { get; set; }
   public virtual DbSet<Magazine> Magazines { get; set; }
   public virtual DbSet<Comic> Comics { get; set; }
}

And finally I add-migration Initial.

My migration now creates separate tables (TPC) for each type. Perfect.

The problem comes when I try to configure my base class using fluent API.

I add a configuration for Entity

class EntityConfiguration : IEntityTypeConfiguration<Entity>
{
    public void Configure(EntityTypeBuilder<Entity> builder)
    {
       builder.HasKey(e => e.Id);
       builder.Property(e => e.Title).IsRequired();
    }
} 

The idea being that I will now only need to configure the base Entity, and all tables for the subclasses should pick up the configuration.

I add my configuration to the DbContext OnModelCreating method.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   base.OnModelCreating(modelBuilder);
   modelBuilder.ApplyConfiguration(new EntityConfiguration());
}

When I add-migration, I end up with this

migrationBuilder.CreateTable(
                name: "Entity",
                columns: table => new
                {
                    Edition = table.Column<int>(nullable: true),
                    Name = table.Column<string>(nullable: true),
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    Discriminator = table.Column<string>(nullable: false),
                    Title = table.Column<string>(nullable: false),
                    Frequency = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Entity", x => x.Id);
                });

By trying to configure the base class, EF is now going down the TPH route, and creating a single table for Entity with a discriminator column.

Is there a way to avoid this? Is it even possible to configure the base class and have all concrete tables pick up the configuration on the base class but create tables for the subclasses?

NOTE: I have tried configuring the Entity directly in the DbContext OnModelCreating method, instead of using a separate configuration class, but this behaves just the same.

The EF Core docs actually say TPC is not supported, which is strange because it does create separate tables for the subclasses until I try to configure the base class.

I have tried using Ignore() to suppress the TPH but this has no effect.

Example given is not real-world. My actual project has many more classes which all have common properties and relationships, so I want to avoid having to configure the same things over and over again.

1条回答
叼着烟拽天下
2楼-- · 2019-08-14 08:15

You are correct in saying that EF Core does not support TPC as of writing.

However, there does appear to be a way to get around this (for generating the 'Up' script at least).

Remove the FluentAPI registrations and use Annotations on the Entity class's properties:

public abstract class Entity
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Title { get; set; }
}

Also, because TPC is Table Per (Concrete) Class, it's good practice to make the class you're inheriting from abstract.

查看更多
登录 后发表回答