23

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.

TheDirtyJenks
  • 399
  • 1
  • 3
  • 12

2 Answers2

57

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);
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • 1
    You hit the nail on the head. I was specifically seeking how to implement that constrained generic method and pass concrete entity types. Thank you so much for your time! – TheDirtyJenks Apr 24 '18 at 22:24
  • 1
    @Ivan how would I do that without leveraging the `IEntityTypeConfiguration` ?https://stackoverflow.com/questions/53275567/how-to-apply-common-configuration-to-all-entities-in-ef-core – ibubi Nov 15 '18 at 18:15
  • 1
    @ibubi I've posted the answer there. – Ivan Stoev Nov 15 '18 at 19:18
  • I have an abstract entity that has a collection property and is implemented by many concrete subclasses. Do I have to configure this relationship separately for each of these subclasses? – Shimmy Weitzhandler Dec 18 '18 at 05:33
  • @Shimmy In case you are not using EF/database inheritance strategy, yes, because it will be a separate relationship. – Ivan Stoev Dec 18 '18 at 08:01
  • 1
    Thank you for this! I was trying and could not get it to work. I initially missed the "base.Configure..." entry, adding that and it all works perfectly. – user1489765 Jul 03 '19 at 19:57
  • 1
    Great answer - this helped me a lot. Thanks! – Ed Graham Jul 11 '19 at 13:48
  • Does this still apply to efcore 3.1 ? – D.Man May 04 '21 at 10:17
  • @D.Man Yes, all is the same in that regard, including EFC 3.x and 5.x – Ivan Stoev May 04 '21 at 10:21
  • With Fluent NHibernate, you can use the subclass to map your models with inheritance. This will result in having two tables in the database, one for the base class and the other for the child class. The child table do not have duplicate information from the parent table. Is this possible with EF core using this `IEntityTypeConfiguration` approach? – Bloggrammer Mar 16 '22 at 01:39
  • @Blogrammer The approach here is for configuring properties of a base class not mapped as *entity* (as mentioned in the beginning of the answer). While what you are asking for is [entity inheritance mapping](https://learn.microsoft.com/en-us/ef/core/modeling/inheritance), more specifically the TPT strategy. These are configured differently, and actually each entity class (base or derived) is configured separately, so they don't need to share/call code for base class like here (actually it is disallowed). – Ivan Stoev Mar 16 '22 at 08:47
  • Thanks for sharing the link, @IvanStoev. Exactly what I wanted – Bloggrammer Mar 16 '22 at 13:41
0

You can do this using reflection in the OnModelCreating method and generic Configuration class for the base class without the need of calling base.Configure(builder) for each entity:

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

In the OnModel creating find classes inherit of the base class, and create an instance of the configuration like the following:

      var subTypes= Assembly
           .GetAssembly(typeof(Status))
           .GetTypes()
           .Where(c => c.IsSubclassOf(typeof(Status)));
        foreach (var item in subTypes)
        {
            Type baseEntityConfigGenericType= typeof(StatusConfiguration<>);
            Type[] typeArgs = { item };
            Type constructed = baseEntityConfigGenericType.MakeGenericType(typeArgs);
            dynamic o = Activator.CreateInstance(constructed);
            if (builder.Model.FindEntityType(item) != null)
                builder.ApplyConfiguration(o);
}

Now each time you add a new subtype, during the migration, it will take into account the base type specs automatically.