Entity Framework Core 2.0: How to configure abstract base class once

TheDirtyJenks picture TheDirtyJenks · Apr 23, 2018 · Viewed 9.8k times · Source

I have a base model:

public abstract class Status
{
     public string updateUserName { get; set; }
}

Then a model which extends the base model defined above:

public class Item : Status
{
     public int Id { get; set; }
     public string Description { get; set; }
}

Then I have defined configuration classes for each:

public class ItemConfiguration : IEntityTypeConfiguration<Item>
{
    public void Configure(EntityTypeBuilder<Item> builder)
    {
        builder.ToTable("Item", "dbo").HasKey(c => c.Id);
        builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
    }
}

public class StatusConfiguration : IEntityTypeConfiguration<Status>
{
    public void Configure(EntityTypeBuilder<Status> builder)
    {
        builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
    }

Now, I have the following Context class:

public class TestDbContext : DbContext
{
    public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
    {
    }

    public DbSet<Item> Item { get; set; }

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

I'm trying to figure out how to apply the Status model configurations defined in the StatusConfiguration class to all the models that extend to it (only one in this example: Item). I would like to avoid defining the same Status model configuration every time it gets used. The Status model will essentially be meta data associated with each Item record (i.e. one Item table in database containing all properties defined in both models; nothing more, and nothing less).

For example, my current implementation is the following ItemConfiguration class without using the StatusConfiguration class:

public class ItemConfiguration : IEntityTypeConfiguration<Item>
{
    public void Configure(EntityTypeBuilder<Item> builder)
    {
        builder.ToTable("Item", "dbo").HasKey(c => c.Id);
        builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
        builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
    }
}

That current implementation works correctly and migrates to the database as intended. I'm simply looking for a more manageable way going forward.

My assumption is that I could extend the ItemConfiguration class to include the StatusConfiguration class but cannot find an example of that method online. I'm hoping someone with a little more experience could kindly point me in the right direction?

Let me know if additional information would be helpful.

Answer

Ivan Stoev picture Ivan Stoev · Apr 24, 2018

If I understand correctly, the Status is just a base class and not a base entity participating in Database Inheritance.

In such case it's important to never refer to Status class directly inside entity model and configuration, i.e. no DbSet<Status>, no navigation properties of type Status or ICollection<Status>, no modelBuilder.Entity<Status>() calls and no IEntityTypeConfiguration<Status>.

Instead, you always have to refer to the concrete types inheriting from the Status. In order to reuse configuration code, you should use constrained generic methods or classes and pass the concrete entity types.

Since you are using IEntityTypeConfiguration classes, probably the most natural is to make your StatusConfiguration class generic:

public class StatusConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
    where TEntity : Status
{
    public virtual void Configure(EntityTypeBuilder<TEntity> builder)
    {
        builder.Property(c => c.updateUserName).IsRequired().HasMaxLength(50);
    }
}

and let derived entity configuration classes derive from it:

public class ItemConfiguration : StatusConfiguration<Item>
{
    public override void Configure(EntityTypeBuilder<Item> builder)
    {
        base.Configure(builder); // <--
        builder.ToTable("Item", "dbo").HasKey(c => c.Id);
        builder.Property(c => c.Description).IsRequired().HasMaxLength(100);
    }
}