Analyzer rule
Spec: LC011 - Entity Missing Primary Key
EF Core LINQ performance analyzer and Roslyn analyzer for catching query issues at compile time.
Spec: LC011 - Entity Missing Primary Key
Goal
Detect DbSet<TEntity> entity types that EF Core would treat as regular tracked entities but that have no primary key definition.
The Problem
EF Core needs a primary key for tracked entities so it can identify rows, perform updates/deletes, resolve relationships, and maintain identity in the change tracker. A missing key usually fails model validation at startup or leaves the entity usable only as a keyless query type when that was not intended.
Example Violation
public class AppDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
}
public class Product
{
public string Name { get; set; }
}
Supported Safe Shapes
LC011 treats these as valid primary-key or intentional opt-out patterns:
- Convention keys: public mapped
Idor{EntityName}Idproperties. - Data annotations: public mapped properties with
System.ComponentModel.DataAnnotations.KeyAttribute. - EF Core composite keys: class-level
Microsoft.EntityFrameworkCore.PrimaryKeyAttributewhere all referenced properties exist, are public, mapped, and key-compatible. - Fluent API keys in
OnModelCreating:modelBuilder.Entity<TEntity>().HasKey(...), scoped local builder variables, and chained builder calls such asentity.ToTable("Products").HasKey(...). - Applied configurations: inline or local
modelBuilder.ApplyConfiguration(...)calls, andApplyConfigurationsFromAssembly(...)when the assembly argument is proven to be the current source assembly (typeof(LocalType).Assembly,System.Reflection.Assembly.GetExecutingAssembly(),global::System.Reflection.Assembly.GetExecutingAssembly(), ausing Assembly = System.Reflection.Assemblyalias, or an unshadowed local/readonly-member alias of those expressions) and the configuration’sConfiguremethod callsHasKey(...)orHasNoKey()for its ownEntityTypeBuilder<TEntity>. - Intentional keyless types:
Microsoft.EntityFrameworkCore.KeylessAttributeorHasNoKey(). - Owned types:
Microsoft.EntityFrameworkCore.OwnedAttribute, generic ownership such asOwnsOne<Address>(...), and inferred ownership such asOwnsOne(e => e.Address)/OwnsMany(e => e.Items).
Attributes are namespace-checked. A custom class named KeyAttribute, PrimaryKeyAttribute, or KeylessAttribute does not suppress LC011.
Fixes
Preferred fixes are domain-specific:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
modelBuilder.Entity<Product>().HasKey(p => p.ProductCode);
modelBuilder.ApplyConfiguration(new ProductConfiguration());
public sealed class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasKey(p => p.ProductCode);
}
}
The code fix offers public int Id { get; set; } only when the entity is source-editable and has no existing Id member. It intentionally avoids generating a duplicate Id when the current member is private, ignored, or a navigation property; choose the correct domain key manually in those cases.
Analyzer Logic
ID: LC011
Category: Design
Severity: Warning
LC011 starts from DbSet<TEntity> members on source DbContext types, checks the entity’s inheritance chain for valid mapped key properties, then folds in keyless/owned/fluent configuration discovered from the analyzed context. Standalone IEntityTypeConfiguration<TEntity> classes are not trusted unless the context applies them, preventing one context’s configuration from suppressing diagnostics in another context.
Malformed self-referential builder locals in OnModelCreating are treated as unresolved builder expressions rather than followed recursively. This keeps live IDE analysis from crashing on incomplete edits such as a local variable that accidentally refers to itself before it has been declared.