Two Ways to Configure the Model
EF Core offers two complementary ways to shape your model beyond convention: Data Annotations, which are attributes like [Required] or [MaxLength(100)] applied directly on entity properties, and the Fluent API, expressed in OnModelCreating (or IEntityTypeConfiguration<T> classes) using methods like HasMaxLength and IsRequired. Both ultimately update the same underlying EF Core model, but when the two disagree on the same property, Fluent API configuration always wins.
Cricket analogy: Choosing between Data Annotations and Fluent API is like a batsman adjusting stance via natural instinct (annotations, quick and inline) versus a coach's detailed video-analysis session (Fluent API, more powerful) - when they conflict, the coach's final call, like Fluent API, overrides instinct.
Data Annotations
Data Annotations live in System.ComponentModel.DataAnnotations and System.ComponentModel.DataAnnotations.Schema namespaces, letting you write [Required], [MaxLength(200)], [Column("product_name")], and [ForeignKey("CategoryId")] directly above a property. They're convenient because the same [Required] attribute also drives ASP.NET Core MVC/Razor Pages client-side validation, but they can't express relational-only concepts like table splitting, check constraints, or alternate keys.
Cricket analogy: Putting [Required] on a property is like a team's non-negotiable rule that every player must wear a helmet against pace bowling - simple, visible, and enforced everywhere the rule applies, both on the field and in team selection meetings.
Fluent API in OnModelCreating
The Fluent API is invoked inside DbContext.OnModelCreating(ModelBuilder modelBuilder), or better, organized into IEntityTypeConfiguration<TEntity> classes applied via modelBuilder.ApplyConfigurationsFromAssembly(...). It can express everything Data Annotations can plus relational-only features: composite keys (HasKey), indexes (HasIndex), check constraints (ToTable(t => t.HasCheckConstraint(...))), value conversions, and query filters (HasQueryFilter) that annotations simply cannot express.
Cricket analogy: Fluent API in OnModelCreating is like a team's full strategic team meeting before a Test series where every tactical detail - field placements, bowling rotations, batting order - is planned in one coordinated session rather than scattered pre-match notes.
public class Product
{
public int Id { get; set; }
[Required, MaxLength(200)]
public string Name { get; set; } = null!;
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
}
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasIndex(p => p.Name).IsUnique();
builder.Property(p => p.Price).HasPrecision(18, 2);
builder.HasQueryFilter(p => !p.IsDeleted);
}
}Prefer IEntityTypeConfiguration<T> classes over a single sprawling OnModelCreating method - each entity's configuration lives next to related configuration, and modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly) picks them all up automatically.
When a Data Annotation and a Fluent API call configure the same property differently, Fluent API silently wins with no compiler warning - relying on annotations alone for anything relational-specific (indexes, conversions, check constraints) will fail because those features have no annotation equivalent.
- Data Annotations and Fluent API both update the same underlying EF Core model.
- On conflict between the two, Fluent API configuration takes precedence.
- Data Annotations also drive ASP.NET Core client-side validation, giving double duty.
- Fluent API is required for composite keys, indexes, check constraints, and value conversions.
- IEntityTypeConfiguration<T> classes keep configuration organized per entity.
- ApplyConfigurationsFromAssembly automatically discovers and applies all configuration classes.
Practice what you learned
1. Which configuration approach wins when a Data Annotation and Fluent API configure the same property differently?
2. Which of these can ONLY be configured via Fluent API, not Data Annotations?
3. What is the purpose of IEntityTypeConfiguration<TEntity>?
4. Which method wires up all IEntityTypeConfiguration<T> classes in an assembly automatically?
Was this page helpful?
You May Also Like
Entity Classes and Conventions
Learn how EF Core discovers and maps plain C# classes to database tables using convention-based modeling, including primary keys, shadow properties, and backing fields.
One-to-Many Relationships
Learn how EF Core models one-to-many relationships through navigation properties and foreign keys, including required vs optional relationships and delete behavior.
Value Conversions and Owned Types
Learn how EF Core value converters translate between C# and database types, and how owned types model value objects like Address without giving them independent identity.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics