Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 613
TheDirtyJenks Avatar asked Apr 23 '18 21:04

TheDirtyJenks


1 Answers

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);
    }
}
like image 108
Ivan Stoev Avatar answered Nov 06 '22 03:11

Ivan Stoev