Language: C# | Type: BUG | Severity: Blocker
Tags: entity-framework-core, entity-framework, orm, database
This rule raises an issue when an entity class declares more than one property annotated with the Key attribute in an attempt to
define a composite primary key.
Entity Framework Core does not interpret multiple Key attributes as a composite primary key. When an entity has two or more
Key-decorated properties, EF Core throws an InvalidOperationException while building the model, the first time the
DbContext is used.
Because the model cannot be built, the application crashes the first time the DbContext is instantiated. This is not limited to the
affected entity: nothing that depends on the context can run until the key is configured correctly.
Declare the composite key in a single place. Either apply the PrimaryKey attribute to the entity class, or configure the key with the
Fluent API by overriding OnModelCreating and calling HasKey.
public class Student
{
[Key]
public string LastName { get; set; } // Noncompliant
[Key]
public string FirstName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Apply the PrimaryKey attribute to the entity class:
[PrimaryKey(nameof(LastName), nameof(FirstName))]
public class Student
{
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Alternatively, configure the key with the Fluent API in OnModelCreating:
public class Student
{
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
public class SchoolContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasKey(s => new { s.LastName, s.FirstName });
}
}
In larger projects, move each entity’s Fluent API configuration out of OnModelCreating into a dedicated class that implements EF
Core’s IEntityTypeConfiguration<TEntity>. Its Configure(EntityTypeBuilder<TEntity> builder) method receives the
same builder you would use inline, so the composite key is still declared with builder.HasKey(…); you then register the class with
modelBuilder.ApplyConfiguration(…) in OnModelCreating. This keeps the entity free of mapping concerns and gathers its
configuration in one focused place.