EF Core Entity Configuration Standards · EF-02
Always set a DbContext-wide convention for maximum string lengths · EF-02.1 · MUST
Restrict EF Core from defaulting to NVARCHAR(MAX) by configuring the convention at DB Context-level. By default, EF Core maps string properties to nvarchar(max) on SQL Server. This comes with several downsides, such as SQL Server allocating resources in anticipation for every row to hold up to 2 GB of data.
Good Example: DB Context-level restriction
internal class CommerceDatabaseContext(DbContextOptions<CommerceDatabaseContext> options) : DbContext(options)
{
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<string>()
.HaveMaxLength(256);
}
}Always set string lengths explicitly when it differs from the convention · EF-02.2 · MUST
Always specify a maximum string length using HasMaxLength when a string field’s max length is known to differ from the DBContext’s convention.
Good Example: Explicit string length
public class BookConfiguration : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.Property(b => b.Title)
.HasMaxLength(200);
builder.Property(b => b.Isbn)
.HasMaxLength(13);
builder.Property(b => b.Contents)
.HasColumnType("nvarchar(max)");
}
}Bad Example: Lengths left to the convention when they should differ
Assuming a global default max length of
nvarchar(256)is configured (see EF-02.1).
public class BookConfiguration : IEntityTypeConfiguration
{
public void Configure(EntityTypeBuilder<Book> builder)
{
// Known maximum of 13 characters, but will inherit nvarchar(256).
builder.Property(b => b.Isbn);
// Should be unbounded but will also inherit nvarchar(256).
builder.Property(b => b.Contents);
}
}Set decimal precision and scale explicitly · EF-02.3 · MUST
Always specify the precision of a decimal column, even when using the default, to provide clear intent of the values contained.
Decimals have a default precision of 18 and a scale of 2. Meaning the decimal is a maximum of 18 digits long and a maximum of 2 decimal places, which can lead to rounding errors.
Good Example: Explicit decimal precision
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.Property(o => o.Total)
.HasPrecision(18, 2);
builder.Property(o => o.ExchangeRate)
.HasPrecision(18, 6);
}
}Bad Example: Default decimal precision
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
// Unclear intent for the values contained.
builder.Property(o => o.Total);
// Values will be silently truncated or rounded unexpectedly.
builder.Property(o => o.ExchangeRate);
}
}Register configuration via ApplyConfigurationsFromAssembly · EF-02.4 · MUST
ApplyConfigurationsFromAssembly · EF-02.4 · MUSTRegister all IEntityTypeConfiguration<T> implementations in one line using ApplyConfigurationsFromAssembly. This avoids the maintenance burden of manually calling ApplyConfiguration for each new entity, and keeps OnModelCreating short and focused.
Good Example: Bulk registration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}Bad Example: Manual registration per entity
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Every new configuration class must be added here manually,
// and it is easy to forget one.
modelBuilder.ApplyConfiguration(new BookConfiguration());
modelBuilder.ApplyConfiguration(new AuthorConfiguration());
modelBuilder.ApplyConfiguration(new OrderConfiguration());
modelBuilder.ApplyConfiguration(new CustomerConfiguration());
}Define explicit OnDelete behaviour · EF-02.5 · MUST
Explicitly call OnDelete(DeleteBehavior.*) while configuring entity relationships to avoid EF Core’s default behaviour and declare intent and ensure behaviour is consistent across environments.
Good Example: Explicit delete behaviour
public class BookConfiguration : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
builder.HasOne(b => b.Author)
.WithMany(a => a.Books)
.HasForeignKey(b => b.AuthorId)
.OnDelete(DeleteBehavior.Restrict);
}
}Bad Example: Implicit delete behaviour
public class BookConfiguration : IEntityTypeConfiguration<Book>
{
public void Configure(EntityTypeBuilder<Book> builder)
{
// No OnDelete — the behaviour is inferred from FK nullability and
// varies by provider. Deleting an Author may cascade-delete every
// Book, or block the delete entirely, depending on the database.
builder.HasOne(b => b.Author)
.WithMany(a => a.Books)
.HasForeignKey(b => b.AuthorId);
}
}