Customizing EF Core 2.0+ Entity/Table Mapping with IEntityTypeConfiguration

tldr;

In EF Core 2.0, you can now rip out your per-Entity Fluent API table customizations into separate classes instead of having them all in the OnModelCreating method of your DbContext.  You do this by inheriting from IEntityTypeConfiguration<T>.

 

Before In EF Core 1.x, note lines 13-33:


public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<OrderItem> Orders { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customizations must go after base.OnModelCreating(builder)
builder.Entity<ApplicationUser>(config =>
{
// Override nvarchar(max) with nvarchar(15)
config.Property(u => u.PhoneNumber).HasMaxLength(15);
// Make the default table name of AspNetUsers to Users
config.ToTable("Users");
});
builder.Entity<OrderItem>(config =>
{
// Make the default column type datetime over datetime2 (for some reason).
config.Property(o => o.DateTimeOrdered).HasColumnType("datetime");
// Make the default value 1 for the Quantity property
config.Property(o => o.Quantity).HasDefaultValue(1);
// Make the Primary Key associated with the property UniqueKey
config.HasKey(o => o.UniqueKey);
});
// Imagine a ton more customizations
}
}

 

After in EF Core 2.0+, note lines 13, 14, and the 2 extra files:


public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<OrderItem> OrderItems { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customizations must go after base.OnModelCreating(builder)
builder.ApplyConfiguration(new ApplicationUserConfig());
builder.ApplyConfiguration(new OrderItemConfig());
// Imagine a ton more customizations
}
}


public class ApplicationUserConfig : IEntityTypeConfiguration<ApplicationUser>
{
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
// Override nvarchar(max) with nvarchar(15)
builder.Property(u => u.PhoneNumber).HasMaxLength(15);
// Make the default table name of AspNetUsers to Users
builder.ToTable("Users");
}
}


public class OrderItemConfig : IEntityTypeConfiguration<OrderItem>
{
public void Configure(EntityTypeBuilder<OrderItem> builder)
{
// Make the default column type datetime over datetime2(for some reason).
builder.Property(o => o.DateTimeOrdered).HasColumnType("datetime");
// Make the default value 1 for the Quantity property
builder.Property(o => o.Quantity).HasDefaultValue(1);
// Make the Primary Key associated with the property UniqueKey
builder.HasKey(o => o.UniqueKey);
}
}

 

The latter scales better to a big application with lots of entities.

 

The Problem

Let’s say you want to make a customization to one of your EF Core 2.0 properties and how it maps to your underlying database.  Some customization scenarios include changing the default table name, changing the max length of the column, etc.

 

There are two ways of doing this.  You could either choose the Data Annotations way or using the Fluent API.  I personally like the Fluent API, so that my models aren’t littered with attributes.  It also decouples my models from Entity Framework, in case I choose to use Dapper or another ORM down the road.

 

Prior to EF Core 2.0, to use the Fluent API you would have to “inline” any customizations in your OnModelCreating method.  This works ok for small projects, but as you add tons of tables to your DbContext, this becomes a little hard to manage.

 

This would look something like this:


public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<OrderItem> Orders { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customizations must go after base.OnModelCreating(builder)
builder.Entity<ApplicationUser>(config =>
{
// Override nvarchar(max) with nvarchar(15)
config.Property(u => u.PhoneNumber).HasMaxLength(15);
// Make the default table name of AspNetUsers to Users
config.ToTable("Users");
});
builder.Entity<OrderItem>(config =>
{
// Make the default column type datetime over datetime2 (for some reason).
config.Property(o => o.DateTimeOrdered).HasColumnType("datetime");
// Make the default value 1 for the Quantity property
config.Property(o => o.Quantity).HasDefaultValue(1);
// Make the Primary Key associated with the property UniqueKey
config.HasKey(o => o.UniqueKey);
});
// Imagine a ton more customizations
}
}

It looks ugly just for two tables… imagine 10, 20, or even 100!  …Although maybe you should have multiple DbContexts at that point, but that’s beside the point.

 

This obviously doesn’t scale great.  You could rip out each configuration into your own custom classes or static methods, but now there is a built-in solution in EF Core 2.0+.

 

The Solution

With EF Core 2.0 and above, you can now implement the interface IEntityTypeConfiguration<T> where T is the Model you’re configuring.  After creating that class with the necessary customizations, you need to wire that up to your DbContext’s OnModelCreating method by calling builder.ApplyConfiguration(new ConfigClass).  It’s important to make sure that your customizations come after the base.OnModelCreating(builder) call.

 

It looks something like this, note lines 13, 14, and the two extra files:


public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<OrderItem> OrderItems { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customizations must go after base.OnModelCreating(builder)
builder.ApplyConfiguration(new ApplicationUserConfig());
builder.ApplyConfiguration(new OrderItemConfig());
// Imagine a ton more customizations
}
}


public class ApplicationUserConfig : IEntityTypeConfiguration<ApplicationUser>
{
public void Configure(EntityTypeBuilder<ApplicationUser> builder)
{
// Override nvarchar(max) with nvarchar(15)
builder.Property(u => u.PhoneNumber).HasMaxLength(15);
// Make the default table name of AspNetUsers to Users
builder.ToTable("Users");
}
}


public class OrderItemConfig : IEntityTypeConfiguration<OrderItem>
{
public void Configure(EntityTypeBuilder<OrderItem> builder)
{
// Make the default column type datetime over datetime2(for some reason).
builder.Property(o => o.DateTimeOrdered).HasColumnType("datetime");
// Make the default value 1 for the Quantity property
builder.Property(o => o.Quantity).HasDefaultValue(1);
// Make the Primary Key associated with the property UniqueKey
builder.HasKey(o => o.UniqueKey);
}
}

 

That looks much better and scales much nicer.

If you use the “inline” way today, you should be able to convert to the IEntityTypeConfiguration way without impacting the rest of your app or changing any schema.  The API is the exact same in both formats (i.e. it’s HasKey in both spots to change the Primary Key).

 

Reverse Engineering from Existing Database

While you could handle your customization in EF Core 1 via custom static methods or your own hand rolled classes for maintainability purposes, now we have a consistent way of doing this across all EF Core Projects.  That enables awesome things.

Such as the EF team is starting to work on adding this as an option when you Reverse Engineer classes from an existing database and have it auto-generate your customizations for you into these separate IEntityTypeConfiguration<T> classes.  It’s currently slated for the EF Core 2.1 release that is likely to be released late this year or early next year.  Obviously no guarantees that EF Core 2.1 lands at that time or that it includes this feature.  However, it’s awesome that this is coming down the pipe.

 

This is also possible in EF 6

One final thing I’d like to mention is that you could do this same thing in EF 6.  You just have to inherit from the EntityTypeConfiguration<T> abstract class (not an interface like EF Core), and then put your configuration in the constructor.  There are many examples out there on how to do that and is outside of the scope of this post, but just thought I’d pass along in case you like this pattern and are using EF 6 today.

 

Hope this helps!

17 thoughts on “Customizing EF Core 2.0+ Entity/Table Mapping with IEntityTypeConfiguration

  1. Thank you very much Scott. I’m learning EF Core at the moment and it’s always a surprise bonus to find such well thought out and clearly written articles like this one. You’ve really helped me here.

    I like the convention of putting my configurations (customisations) into their own classes in an “EntityConfigurations” namespace and am now considering creating a generic customisation class, say for any overarching customisations – setting all string properties across entities to a max length for example. Right now I’m doing that by passing ModelBuilder to my customisation class and then working through all the entities and their properties there (if that makes sense). Is there a ‘best practice’ approach that you can think of if you have a few seconds?

    Anyway – thank you again. Your efforts are much appreciated here in Sydney, Australia!

    Cheers, Dave

    • PS. A better use case for generic customisations might be to make a global Unicode setting for text fields, or perhaps some global date formatting. My real interest is perhaps more academic if I’m honest! 🙂 Cheers – Dave

  2. Thanks for the kind words David. Glad it helped.

    In EF Core there is no built-in way to do this like in EF6 where you could use modelBuilder.Properties().Configure(whatever).

    However, you can use Reflection to do this. See this answer here: https://stackoverflow.com/a/41427828

    Regarding defaulting to varchar over nvarchar, you’ll have to do the same idea using reflection. This answer should get you close – https://stackoverflow.com/a/41469383

    I’ve done this exact thing at work w/ EF Core, but I don’t have easy access to the code at the moment.

    Hope this helps!

    Scott

  3. I used to use EntityConfigs but now Im trying to in Core 2.0 but there is no way to set the ForeignKeys that I can find.

  4. Scott Could U provide some example project ??
    This article is very interesting !
    What about adding dbset dynamically! To make assembly load and use model entity ???

  5. Thanks for this – I had been waiting for this feature in EF Core – nice to see a clear example of how it is implemented – some small changes since EF 6.

  6. Hello Scott

    I have a base EntityBaseTypeConfiguration class that all my entities inherit from as well as implementing IEntityTypeConfiguration. How can I call this base class? I tried the below but I am getting an error “cannot convert from ‘Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder’ to ‘Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder'”
    in the line base.Configure(builder).

    class EntityNameTypeConfiguration : EntityBaseTypeConfiguration, IEntityTypeConfiguration
    {
    public void Configure(EntityTypeBuilder builder)
    {
    base.Configure(builder);

    builder.HasIndex(c => c.Name)
    .IsUnique();

    builder.Property(c => c.IsActive)
    .HasDefaultValue(true);
    }
    }

Leave a Reply