Single Responsibility Principle in Entity Framework Configurations
Entity Framework (EF) is my go-to Object-Relational Mapping (ORM) tool for .NET development, especially when using a SQL Server Database. As the EF ecosystem has matured, various ways to configure entities have emerged. One such method is the IEntityTypeConfiguration<T>
interface. In today's blog post, we'll take a sneak peek into why IEntityTypeConfiguration<T>
is your codebase's tidying-up fairy and how you can employ it for a clean, maintainable context configuration.
But first,…:
“Why did the database admin leave his job? Because he didn’t like the relationship with tables!” 😂
Alright, let’s dive in.
The Problem with Inline Entity Configuration
To paint a picture, imagine a library system DbContext
with all entity configurations set up inline. As your application grows and more entities are added, this DbContext
can become unwieldy. For instance:
public class ApplicationContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
// ... more DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Book configurations
modelBuilder.Entity<Book>().HasKey(b => b.Id);
modelBuilder.Entity<Book>().Property(b => b.Title).IsRequired().HasMaxLength(200);
// ... other Book configurations
// Author configurations
modelBuilder.Entity<Author>().HasKey(a => a.Id);
modelBuilder.Entity<Author>().Property(a => a.Name).IsRequired().HasMaxLength(100);
// ... other Author configurations
// ... and many more entity configurations
}
}
Imagine having 20 entities or more. The OnModelCreating
method becomes a giant maze of configurations, making it hard to read, maintain, and modify. If our DbContext
were a book, it'd be the "War and Peace" of code—overwhelmingly long and intimidating.
Introducing IEntityTypeConfiguration<T>
IEntityTypeConfiguration<T>
provides a way to separate out the configuration of each entity into its class. Each of these classes will focus solely on configuring a single entity, thereby promoting the Single Responsibility Principle (SRP).
Let’s refactor the above example using IEntityTypeConfiguration<T>
:
1. Create Configuration Classes
public class BookConfiguration : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.HasKey(b => b.Id);
builder.Property(b => b.Title).IsRequired().HasMaxLength(200);
// ... other Book configurations
}
}
public class AuthorConfiguration : IEntityTypeConfiguration<Author>
{
public void Configure(EntityTypeBuilder<Author> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.Name).IsRequired().HasMaxLength(100);
// ... other Author configurations
}
}
2. Refactor the DbContext
public class ApplicationContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
// ... more DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new BookConfiguration());
modelBuilder.ApplyConfiguration(new AuthorConfiguration());
// ... apply other configurations
}
}
…or better still…
public class ApplicationContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
// ... more DbSets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Applies all configurations from the assembly of the specified type (e.g., BookConfiguration)
modelBuilder.ApplyConfigurationsFromAssembly(typeof(BookConfiguration).Assembly);
}
}
When using ApplyConfigurationsFromAssembly
, you're telling EF Core to scan the assembly where your specified type (in this case, BookConfiguration
) resides. EF Core will then look for all classes that implement IEntityTypeConfiguration<T>
and apply their configurations automatically.
Benefits:
- Simplicity: It abstracts away the reflection code, making the DbContext cleaner and easier to read.
- Automatic Detection: When adding a new entity configuration, there’s no need to remember to register it manually; it’s detected and applied automatically, provided it’s in the specified assembly.
Considerations:
- Assembly Scanning: Make sure the type you provide (e.g.,
BookConfiguration
) is in the same assembly as all your other configurations. Otherwise, they won't be detected. - If you split configurations across multiple assemblies, you'll need to call
ApplyConfigurationsFromAssembly
multiple times, once for each assembly.
Benefits of Using IEntityTypeConfiguration<T>
- Separation of Concerns: Each entity’s configuration is now in its class and can even be in its own file. It’s more readable and adheres to the SRP.
- Easier Maintenance: Need to modify the configuration of an entity? Just open its configuration class/file.
- Reusable Configurations: If you have multiple contexts, you can reuse the same configurations without duplication.
Wrapping Up
Just like how Marie Kondo helps people declutter their homes, IEntityTypeConfiguration<T>
helps declutter your Entity Framework configurations. With each entity configuration in its rightful place, your DbContext becomes a much more joyful space.
And remember:
“Why did the developer keep his DbContext tidy? So he wouldn’t query chaos!” 😜
Keep coding and keep your configurations tidy!