Engineering/Coding Standards/C#/Entity Framework Core/Ef Entity Configuration/

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

Register 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);
    }
}