.claude/skills/ef-core-standards/SKILL.md
Use when designing EF Core entities, relationships, value objects, owned types, inheritance strategies, Fluent API configuration, migrations, schema changes, DbContext design, data access patterns, repository implementations, or querying strategies. Provides domain-specific rules layered on top of base architectural standards.
npx skillsauth add klod68/littlerae ef-core-standardsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Field | Value |
|---|---|
| Name | EF Core Standards |
| Domain | Domain Modeling, ORM, Data Access, Persistence |
| Level | Feature |
| Tags | ef-core, entities, fluent-api, relationships, value-objects, migrations, dbcontext, repository, data-access |
Activate this skill when the task involves:
IEntityTypeConfiguration<T>OwnsOne / OwnsMany)dotnet ef commands or migration scriptsAsNoTracking, projections, includes)These rules layer on top of base architectural standards. On conflict, these win.
<!-- SHARED:rules/ef-core.md -->Applicability: EF Core is a secondary persistence mechanism in this architecture. Use it for lite sites, demos, mocks, and rapid prototypes. For production enterprise applications, the primary persistence strategy is stored-procedure-first via
Truenorth.Components.Persistence(seepersistence.md). The rules below apply when EF Core is the chosen mechanism for a given project.
Apply these rules in addition to _base.md for projects using Entity Framework Core.
DbContext per bounded context — never a god context for the entire application.DbContext is always registered as Scoped — never Singleton or Transient.DbContext outside the Infrastructure layer; wrap it behind a repository interface.DbSet<T> properties are internal — only the Infrastructure layer queries them directly.init for properties that should not change after creation.Order? Order).private constructor for EF, public constructor with required parameters for application code.DbSet<T> or DbContext outside the data/infrastructure layer.IEntityTypeConfiguration<T> classes — never OnModelCreating inline.{Entity}Configuration.cs in an EntityConfigurations/ folder.HasMaxLength — no unbounded nvarchar(max) without justification.OwnsOne / OwnsMany, never a separate table unless required.IEntityTypeConfiguration<T> implementations — never use data annotations on entities.OrderConfiguration : IEntityTypeConfiguration<Order>.OnModelCreating via modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly).[Table], [Column], [Key], [Required] attributes.OnDelete(DeleteBehavior.Restrict) is safer than the default Cascade.OrderItem with Quantity).Order → Customer → Orders, break the cycle by omitting one back-reference.| Relationship | Configuration Pattern |
|---|---|
| One-to-Many | .HasMany(o => o.Items).WithOne(i => i.Order).HasForeignKey(i => i.OrderId) |
| One-to-One | .HasOne(o => o.ShippingAddress).WithOne().HasForeignKey<ShippingAddress>(a => a.OrderId) |
| Many-to-Many (simple) | .HasMany(s => s.Courses).WithMany(c => c.Students) |
| Many-to-Many (with payload) | Explicit join entity with two one-to-many relationships |
OwnsOne / OwnsMany.ToTable() to split if needed.record types or override Equals/GetHashCode.Address, Money, DateRange, EmailAddress, PhoneNumber.// Entity
public class Customer
{
public int Id { get; private set; }
public string Name { get; private set; } = string.Empty;
public Address ShippingAddress { get; private set; } = null!;
}
// Value Object
public record Address(string Street, string City, string State, string ZipCode);
// Configuration
builder.OwnsOne(c => c.ShippingAddress, a =>
{
a.Property(x => x.Street).HasMaxLength(200).IsRequired();
a.Property(x => x.City).HasMaxLength(100).IsRequired();
a.Property(x => x.State).HasMaxLength(2).IsRequired();
a.Property(x => x.ZipCode).HasMaxLength(10).IsRequired();
});
| Strategy | When to Use | Trade-off | |---|---|---| | TPH (Table-Per-Hierarchy) | Few subtypes, similar columns, query across types | Nullable columns for subtype-specific fields | | TPT (Table-Per-Type) | Many subtypes, different columns, normalized schema | JOIN per query — slower reads | | TPC (Table-Per-Concrete) | Independent tables per type, no cross-type queries | No shared table — duplicate columns, fastest reads |
.HasDiscriminator<string>("Type").HasValue<PremiumCustomer>("Premium").HasConversion for enums, strongly-typed IDs, and custom types.HasConversion<string>() for enums stored in the database (readable, queryable).HasConversion(id => id.Value, value => new OrderId(value)).CreatedAt, UpdatedAt, CreatedBy.OnModelCreating: builder.Property<DateTime>("CreatedAt").SaveChangesAsync override.private readonly List<OrderItem> _items = new();.HasIndex(u => u.Email).IsUnique()..HasIndex(o => o.Status).HasDatabaseName("IX_Orders_Status").AddUserEmailIndex, not Migration_20240101.IDataSeeder implementation — never in a migration.AsNoTracking() for all read-only queries — never track entities you will not modify.Include() chains deeper than two levels in a single query; use projections instead.Select() at the query level — never load full entities then map in memory.ToList() / ToArray() inside a loop — batch at the query layer.FromSqlRaw, ExecuteSqlRaw) requires a comment explaining why LINQ was insufficient.IQueryable<T> leaking out.Get(Expression<Func<T,bool>>).| Anti-Pattern | Fix |
|---|---|
| Data annotations on entities ([Required], [MaxLength]) | Use Fluent API in IEntityTypeConfiguration<T> |
| Exposing DbSet<T> as public outside data layer | Access via repository or query service |
| Default cascade delete on all relationships | Explicitly set OnDelete(DeleteBehavior.Restrict) |
| Lazy loading enabled globally | Use eager loading (.Include()) or explicit projections |
| Anemic entities with only getters/setters | Add domain behavior methods; use private setters |
| Navigation property cycles (A → B → A) | Remove one back-reference or use .Ignore() |
| Missing indexes on foreign keys | Always add indexes for FK columns |
| string columns without HasMaxLength | Always specify max length — prevents nvarchar(max) |
| Enum stored as int without conversion | Use HasConversion<string>() for readability |
| context.SaveChanges() called inside a loop | Batch and save once outside the loop |
| Entity returned directly to API layer | Map to DTO before crossing layer boundary |
| Include() for data only sometimes needed | Use separate query or projection |
| Migration modifying previously applied migration | Create a new corrective migration |
| DbContext injected into domain or application services | Inject repository interface instead |
design-patterns.md — Approved GoF patterns: Factory, Repository, Decorator, Strategy, Specificationcqrs.md — CQRS via MediatR, command/query separation, pipeline behaviorsnaming.md — Naming conventions for types, methods, properties, namespacesnullability.md — Nullable reference types, suppression discipline, collection rulespersistence.md — Stored-procedure persistence layer, CrudPersistenceHelper patternstools
Use when cross-cutting concerns (logging, metrics, validation, authorization) are tangled into command handlers or service methods, when building database command pipelines with reorderable concerns, or when HTTP client pipelines or message handlers need composable, independently-replaceable processing stages. Covers ICommandInterceptor interface, InterceptorPipeline with reverse-chain construction, zero-cost Empty sentinel to skip overhead when no interceptors are registered, and ConfigureAwait(false) discipline for library code. Domain: Architecture, Cross-Cutting Concerns. Level: Intermediate. Tags: interceptor, pipeline, middleware, decorator, cross-cutting-concerns.
development
Use when writing integration tests for Razor Pages, MVC, or Minimal API applications to validate routing, middleware, page rendering, and HTTP behavior without a browser or live server, or when adding fast smoke tests to a CI pipeline. Covers WebApplicationFactory<Program> setup with public partial class Program, in-memory test server, AngleSharp HTML parsing, CSS selector assertions, redirect and status code testing, and a shared static fixture pattern for minimal per-test startup overhead. Domain: Testing, ASP.NET Core. Level: Intermediate. Tags: integration-testing, webapplicationfactory, razor-pages, anglesharp, http-testing.
development
Use when designing indexes for new tables, diagnosing slow queries that are not using indexes efficiently, reviewing index fragmentation and maintenance, or when the current indexing strategy results in key lookups, table scans, or missing index warnings. Covers clustered index key selection (narrow, unique, ever-increasing), non-clustered index design for query patterns, covering indexes with INCLUDE columns, filtered indexes for subset queries, composite index column ordering, DMV-based monitoring for missing and unused indexes, and rebuild vs reorganize maintenance thresholds. Domain: Database, Performance. Level: Intermediate. Tags: index, sql-server, covering-index, filtered-index, performance, dmv, maintenance.
development
Use when building a searchable in-memory catalog or registry for documentation sites, admin panels, or type/API browsers where you need keyword matching, fuzzy search, and ranked results without an external search engine or database. Covers RegistryService with weighted scoring across name, description, keywords, and method names; Levenshtein fuzzy matching; synonym expansion; category and subcategory filtering; and singleton DI registration for datasets of hundreds to low thousands of items. Domain: Search, Data Access Patterns. Level: Intermediate. Tags: search, registry, fuzzy-matching, in-memory, catalog, filtering.