# EF Core Entity Configuration Standards · EF-02

## Standard Metadata

- Area: ORM
- Owner: Backend Steering group
- Last reviewed: 2026-09-09
- Next review due from: 2026-12-01
- Enforcement: CodeAnalysis / Rosyln Analyser / code review
- Status: Active

## 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

```cs
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

```cs
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).

```cs
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

```cs
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

```cs
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

```cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
}
```

### Bad Example: Manual registration per entity

```cs
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

```cs
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

```cs
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);
    }
}
```
