8 minlesson

Configuring Models in EF Core

Configuring Models in EF Core

EF Core provides three approaches for configuring how your entity classes map to database tables: conventions, data annotations, and the Fluent API. Understanding when to use each approach is key to building maintainable data layers.

The Three Configuration Approaches

1. Conventions

Conventions are automatic mapping rules that EF Core applies by default based on naming patterns and types. You've already been using them throughout this course:

csharp
1public class Product
2{
3 public int Id { get; set; } // Convention: "Id" → primary key
4 public string Name { get; set; } = ""; // Convention: non-nullable → required
5 public decimal Price { get; set; } // Convention: property name → column name
6}

Conventions require zero configuration code. They handle standard cases like primary keys, column names, and nullability.

2. Data Annotations

Data annotations are attributes placed directly on your entity classes. They override conventions for simple scenarios:

csharp
1[Table("product_catalog")]
2public class Product
3{
4 public int Id { get; set; }
5
6 [Required]
7 [MaxLength(200)]
8 public string Name { get; set; } = "";
9
10 [Column(TypeName = "decimal(10,2)")]
11 public decimal Price { get; set; }
12}

Annotations are visible right next to the property they configure, which makes them easy to discover. However, they mix data access concerns into your domain model.

3. Fluent API

The Fluent API is configured in the OnModelCreating method of your DbContext and provides the most powerful configuration options:

csharp
1protected override void OnModelCreating(ModelBuilder modelBuilder)
2{
3 modelBuilder.Entity<Product>(entity =>
4 {
5 entity.ToTable("product_catalog");
6 entity.Property(p => p.Name)
7 .IsRequired()
8 .HasMaxLength(200);
9 entity.Property(p => p.Price)
10 .HasColumnType("decimal(10,2)");
11 });
12}

The Fluent API keeps configuration separate from your entity classes, and it can express things that annotations cannot.

Configuration Precedence

When multiple configuration approaches are applied to the same property, EF Core follows a strict precedence order:

1Fluent API → Overrides everything
2Data Annotations → Overrides conventions
3Conventions → Default behavior

The Fluent API always wins. If you configure a column as HasMaxLength(100) in the Fluent API and [MaxLength(200)] on the property, the Fluent API value of 100 is used.

When to Use Each Approach

ApproachBest ForLimitations
ConventionsStandard mappings that follow naming patternsCannot handle custom requirements
Data AnnotationsSimple overrides visible alongside the propertyLimited configuration options; mixes concerns
Fluent APIComplex configuration, composite keys, indexes, query filtersRequires separate configuration code

General Guideline

  • Start with conventions for anything that follows standard patterns
  • Use annotations when you need simple validations that also apply outside EF Core (e.g., ASP.NET model validation)
  • Use Fluent API for database-specific configuration that should not pollute your entity classes

What Only the Fluent API Can Do

Several configuration options are only available through the Fluent API:

  • Composite primary keys - HasKey(e => new { e.Key1, e.Key2 })
  • Alternate keys - HasAlternateKey(e => e.Code)
  • Composite indexes - HasIndex(e => new { e.Col1, e.Col2 })
  • Global query filters - HasQueryFilter(e => !e.IsDeleted)
  • Value conversions - HasConversion<string>()
  • Owned types - OwnsOne(e => e.Address)
  • Table splitting and entity splitting
  • Precision and scale for decimals (also available via annotation in EF Core 6+)

These features are the focus of the remaining lessons in this topic.

Organizing Configuration with IEntityTypeConfiguration

As your model grows, placing all Fluent API calls in OnModelCreating becomes unwieldy. EF Core provides the IEntityTypeConfiguration<T> interface to split configuration into separate classes:

csharp
1public class ProductConfiguration : IEntityTypeConfiguration<Product>
2{
3 public void Configure(EntityTypeBuilder<Product> builder)
4 {
5 builder.ToTable("product_catalog");
6 builder.Property(p => p.Name).HasMaxLength(200);
7 }
8}

You apply these configurations from OnModelCreating:

csharp
1protected override void OnModelCreating(ModelBuilder modelBuilder)
2{
3 modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
4}

This single line discovers and applies all IEntityTypeConfiguration<T> implementations in the assembly.

Summary

  • EF Core offers three configuration layers: conventions, data annotations, and Fluent API
  • Fluent API has the highest precedence and the most capabilities
  • Use IEntityTypeConfiguration<T> to organize Fluent API configuration into separate classes
  • The Fluent API is the only way to configure composite keys, query filters, value conversions, and owned types

In the next lessons, you'll learn each of these Fluent API capabilities in detail.