A Cleaner Way To Do Entity Configuration With EF Core

Even with my love for Dapper these days, I often have to break out EF Core every now and again. And one of the things that catches me out is just how happy some developers are to make an absolute meal out of Entity Configurations with EF Core. By Entity Configurations, I mean doing a code first design and being able to mark fields as “Required”, or limit their length, or even create indexes on tables. Let’s do a quick dive and see what our options are and what gives us the cleanest result.

Attribute (Data Annotations) vs Fluent Configuration

So the first thing you notice when you pick up EF Core is that half the documentation tells you you can simply add an attribute to any entity to mark it as required :

[Required]
public string MyField { get; set; }

And then the other half of the documentation tells you you should override the OnModelCreating inside your context and use “Fluent” configuration like so :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
	base.OnModelCreating(modelBuilder);
	modelBuilder.Entity<MyEntity>()
		.Property(x => x.MyField).IsRequired();
}

Which one is correct? Well actually both! But I always push for Fluent Configurations and here’s why.

There is an argument that when you use attributes like [Required] on a property, it also works if your model is being returned/created via an API. e.g. It also provides validation. This is really a moot point. You should always aim to have specific ViewModels returned from your API and not return out your entity models. Ever. I get that sometimes a quick and dirty internal API might just pass models back and forth, between the database and the API, but the fact that attributes work for both is simply a coincidence not an actual intended feature.

There’s also an argument that the attributes you use come from the DataAnnotations library from Microsoft. Many ORMs use this library to configure their data models. So for example if you took an EF Core entity and switched to using another ORM, it may be able to just work out of the box with the same configurations. I mean, this one is true and I do see the point but as we are about to find out, complex configuration simple cannot be done with data annotations alone and therefore you’re still going to have to do rework anyway.

The thing is, Fluent Configurations are *much* more powerful than Data Annotations. Complex index that spans two fields and adds another three as include columns? Not a problem in Fluent but no way to do it in DataAnnotations (Infact Indexes in general got ripped out of attributes and are only just now making their way back in with a much weaker configuration than just using Fluent https://github.com/dotnet/efcore/issues/4050). Want to configure a complex HasMany relationship? DataAnnotations relationships are all about conventions so breaking that is extremely hard whereas in Fluent it’s a breeze. Microsoft themselves have come out and said that Fluent Configuration for EF Core is an “Advanced” feature, but I feel like anything more than just dipping your toe into EF Core, you’re gonna run into a dead end with Data Annotations and have to mix in Fluent Configuration anyway. When it gets to that point, it makes even less sense to have your configuration split across Attributes and Fluent.

Finally, from a purely aesthetic stand point, I personally prefer my POCOs (Plain Old C# Objects) to be free of implementation details. While it’s true that in this case, I’m building an entity to store in a SQL Database, that may not always be the case. Maybe in the future I store this entity in a flat XML file. I think adding attributes to any POCO changes it from describing a data structure, to describing how that data structure should be saved. Then again, things like Active Record exist so it’s not a hard and fast rule. Just a personal preferences.

All rather weak arguments I know but honestly, before long, you will have to use Fluent Configuration for something. It’s just a given. So it’s much better to just start there in the first place.

Using IEntityTypeConfiguration

So if you’ve made it past the argument of Attributes vs Fluent and decided on Fluent. That’s great! But you’ll quickly find that all the tutorials tell you to just keep jamming everything into the “OnModelCreating” method of your Context. Kinda like this :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
	base.OnModelCreating(modelBuilder);
	modelBuilder.Entity<MyEntity>()
		.Property(x => x.MyField).IsRequired();
}

What if you have 10 tables? 20 tables? 50? This class is going to quickly surpass hundreds if not thousands of lines and no amount of comments or #regions is going to make it more readable.

But there’s also a (seemingly) much less talked about feature called IEntityTypeConfiguration. It works like this.

Create a class called {EntityName}Configuration and inherit from IEntityTypeConfiguration<Entity>.

public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
{
	public void Configure(EntityTypeBuilder<MyEntity> builder)
	{
	}
}

You can then put any configuration for this particular model you would have put inside the context, inside the Configure method. The builder input parameter is scoped specifically to only this entity so it keeps things clean and tidy. For example :

public class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity>
{
	public void Configure(EntityTypeBuilder<MyEntity> builder)
	{
		builder.Property(x => x.MyField).IsRequired();
	}
}

Now for each entity that you want to configure. Keep creating more configuration files, one for each type. Almost a 1 to 1 mapping if you will. I like to put them inside an EntityConfiguration folder to keep things nice and tidy.

Finally. Head back to your Context and delete all the configuration work that you’ve now moved into IEntityTypeConfigurations, and instead replace it with a call to “ApplyConfigurationFromAssembly” like so :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
	modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyContext).Assembly);
}

Now you have to pass in the assembly where EF Core can find the configurations. In my case I want to say that they are in the same assembly as MyContext (Note this is *not* DbContext, it should be the actual name of your real context). EF Core will then go and find all implementations of IEntityTypeConfiguration and use that as config for your data model. Perfect!

I personally think this is the cleanest possible way to configure Entities. If you need to edit the configuration for the Entity “Address”, then you know you just have to go the “AddressConfiguration”.  Delete an entity from the data model? Well just delete the entire configuration file. Done! It’s really intuitive and easy to use.

4 thoughts on “A Cleaner Way To Do Entity Configuration With EF Core”

  1. If you have more than one DbContext it doesn’t work event with “typeof(MyContext)”.
    When you execute: “Add-Migration Initial -context MyContext -o Migrations”, the Migration Class include all Configuration in Assembly.

    This way don’t fail, because you explicitly say which Configuration belong to each DbContext
    modelBuilder.ApplyConfiguration(new MyXEntityTypeConfiguration());
    modelBuilder.ApplyConfiguration(new MyYtEntityTypeConfiguration());
    modelBuilder.ApplyConfiguration(new MyZEntityTypeConfiguration());

    Thanks

    Reply
    • Hmmm. That is somewhat true. I think if you have to add each configuration manually it removes a little bit of the magic. I think the main reason I’ve never run into this is because if I have multiple contexts, I generally create new projects for each data context. So there are tradeoffs but I would probably prefer to do a separate project for each context (Which keeps things pretty clean and concise anyway).

      EDIT : I actually ran into this problem today but there is a very simple solution. ApplyConfigurationsFromAssembly also allows a Predicate as a second parameter, so I simply say to specifically ignore entity configurations in a specific folder (Or vice versa). So in my example, I had an AuditContext where I was writing audit logs to a second database, so I simply said :

      modelBuilder.ApplyConfigurationsFromAssembly(typeof(Context).Assembly, x => x.Namespace != "MyProject.Data.EntityConfiguration.Audit");
      
      Reply
  2. Thanks for calling attention to this, Wade. This approach does feel a lot more manageable.

    I took the encapsulation one step further by just having each entity class be it’s own implementation of IEntityTypeConfiguration, i.e.:

    public class MyEntity : IEntityTypeConfiguration
    {
            public string MyProperty { get; set; }
    	
    	void IEntityTypeConfiguration.Configure(
                EntityTypeBuilder builder) 
    	{
    		builder.Property(x => x.MyProperty).IsRequired();
    	}
    }
    
    Reply
  3. Hi thanks for the article.
    Do you write unit test for these configuration to test that the property is set to required, if yes can you tell me how to build the assertion for it. Thanks

    Reply

Leave a Comment