Entity Mapping
The Application Model provides a clean, organized way to configure your Entity Framework Core entities through the IEntityTypeConfiguration<T> interface from Microsoft.EntityFrameworkCore.
This approach separates entity configuration from your DbContext, making your code more maintainable.
Overview
Entity mapping in the Application Model allows you to define how your entities are configured for Entity Framework Core in dedicated classes.
These entity configurations are automatically discovered and applied when your DbContext is created, eliminating the need to override OnModelCreating in every DbContext
that inherits from the BaseDbContext.
Creating Entity Configurations
To create an entity configuration, implement the IEntityTypeConfiguration<T> interface for your entity type:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
// Configure the primary key
builder.HasKey(u => u.Id);
// Configure properties
builder.Property(u => u.Name)
.HasMaxLength(100)
.IsRequired();
builder.Property(u => u.Email)
.HasMaxLength(255)
.IsRequired();
// Configure indexes
builder.HasIndex(u => u.Email)
.IsUnique();
// Configure relationships
builder.HasMany(u => u.Orders)
.WithOne(o => o.User)
.HasForeignKey(o => o.UserId);
}
}
Automatic Discovery and Registration
Entity configurations are automatically discovered and registered when your application starts. The Application Model:
- Discovers all implementations of
IEntityTypeConfiguration<T>in your application - Registers them with dependency injection so they can have dependencies injected
- Applies them automatically during
OnModelCreatingin your DbContext
This happens automatically when you inherit from BaseDbContext:
public class MyDbContext : BaseDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Order> Orders { get; set; }
// No need to override OnModelCreating - entity maps are applied automatically!
}
Dependency Injection Support
Entity configurations support dependency injection, allowing you to inject services that might be needed for configuration:
public class UserConfiguration : IEntityTypeConfiguration<User>
{
private readonly IConfiguration _configuration;
public UserConfiguration(IConfiguration configuration)
{
_configuration = configuration;
}
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(u => u.Id);
// Use injected configuration
var maxNameLength = _configuration.GetValue<int>("User:MaxNameLength", 100);
builder.Property(u => u.Name)
.HasMaxLength(maxNameLength)
.IsRequired();
}
}
Advanced Configuration Examples
Complex Property Configuration
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasKey(o => o.Id);
// Configure value objects
builder.OwnsOne(o => o.ShippingAddress, address =>
{
address.Property(a => a.Street).HasMaxLength(200);
address.Property(a => a.City).HasMaxLength(100);
address.Property(a => a.PostalCode).HasMaxLength(20);
});
// Configure JSON columns
builder.Property(o => o.Metadata)
.HasJsonConversion();
// Configure computed columns
builder.Property(o => o.TotalAmount)
.HasComputedColumnSql("[Quantity] * [UnitPrice]");
}
}
Multiple Entity Configurations
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasKey(p => p.Id);
builder.Property(p => p.Name)
.HasMaxLength(200)
.IsRequired();
builder.Property(p => p.Price)
.HasPrecision(18, 2);
// Table configuration
builder.ToTable("Products", "Catalog");
// Soft delete configuration
builder.HasQueryFilter(p => !p.IsDeleted);
}
}
public class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Name)
.HasMaxLength(100)
.IsRequired();
// Self-referencing relationship
builder.HasMany(c => c.Children)
.WithOne(c => c.Parent)
.HasForeignKey(c => c.ParentId);
}
}
Best Practices
Organization
- One entity configuration per entity: Keep each entity configuration focused on a single entity type
- Descriptive naming: Use clear names like
UserConfiguration,OrderConfiguration, etc. - Logical grouping: Place entity configurations in a dedicated folder (e.g.,
Configuration/orMapping/)
Configuration Guidelines
- Configure all aspects: Include keys, properties, relationships, and constraints
- Be explicit: Don't rely on conventions for critical configurations
- Use meaningful names: Configure table and column names explicitly when needed
- Document complex logic: Add comments for complex mapping logic
Performance Considerations
- Index configuration: Configure indexes for frequently queried properties
- Query filters: Use global query filters for soft delete patterns
- Relationship loading: Configure loading behavior for relationships
Integration with BaseDbContext
When you inherit from BaseDbContext, entity configurations are automatically applied during model creation. The base class:
- Discovers all
DbSet<T>properties on your DbContext - Looks for corresponding
IEntityTypeConfiguration<T>implementations - Applies the configurations during
OnModelCreating
This means you don't need to manually register or apply entity configurations - they work automatically once you implement the interface.
Migration Support
Entity configurations work seamlessly with Entity Framework Core migrations. When you add or modify entity configurations:
- Run
dotnet ef migrations add <MigrationName>to create a migration - The migration will include all changes from your entity configurations
- Run
dotnet ef database updateto apply the changes
The automatic discovery and application of entity configurations ensures that all your configurations are included in migrations without additional setup.