dotnet-efcore-practices
Skill4 starsChanged 10 months agoMITView on GitHub
Use when adding or configuring Entity Framework Core - isolate migrations in a dedicated project excluded from coverage and enforce attribute-linked dedicated type-configuration classes.
In this repository67 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- agents-onboardingSkill
- agent-workitem-automationSkill
- architecture-testingSkill
- aspire-integration-testingSkill
- automated-standards-enforcementSkill
- best-practice-introductionSkill
- branching-strategy-and-conventionsSkill
- broken-windowSkill
- change-risk-rollbackSkill
- ci-cd-conformanceSkill
- component-boundary-ownershipSkill
- contract-consistency-validationSkill
- csharp-best-practicesSkill
- deployment-provenanceSkill
- documentation-as-codeSkill
- documentation-scaffoldSkill
- dotnet-bespoke-code-minimisationSkill
- dotnet-best-practicesSkill
- dotnet-domain-primitivesSkill
- dotnet-healthchecksSkill
- dotnet-logging-serilogSkill
- dotnet-mapping-standardSkill
- dotnet-open-source-first-governanceSkill
- dotnet-source-generation-firstSkill
- dotnet-specification-patternSkill
- dotnet-testing-assertionsSkill
- environment-traceabilitySkill
- finishing-a-development-branch-iddSkill
- greenfield-baselineSkill
- impacted-scope-enforcementSkill
- incremental-change-impactSkill
- incremental-deploymentSkill
- innersource-governance-bootstrapSkill
- issue-driven-deliverySkill
- library-extraction-stabilisationSkill
- local-dev-experienceSkill
- markdown-authorSkill
- modern-csharp-coding-standardsSkill
- monorepo-orchestration-setupSkill
- observability-logging-baselineSkill
- pair-programmingSkill
- persona-switchingSkill
- process-skill-routerSkill
- quality-gate-enforcementSkill
- release-tagging-planSkill
- repo-best-practices-bootstrapSkill
- repository-foundation-bootstrapSkill
- requirements-gatheringSkill
- runtime-tooling-validationSkill
- safe-brownfield-refactorSkill
- scoped-colocationSkill
- security-processesSkill
- selective-build-testSkill
- semantic-version-impactSkill
- session-awareSkill
- skills-first-workflowSkill
- static-analysis-securitySkill
- structured-review-workflowSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
11 rules. About 0 are common, 8 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈8
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 25
Verify project structure: Ensure migrations are in a dedicated *.Persistence.Migrations project
- Very specific: line 26
Configure coverage exclusion: Add <ExcludeFromCodeCoverage>true</ExcludeFromCodeCoverage> to migrations project
- Distinctive: line 28
Link via attributes: Use attribute-based discovery to connect entities to their configurations
- Distinctive: line 31
Verify coverage gate: Run tests to confirm migrations project excluded from coverage reports
- Very specific: line 32
Add repository tests: Use xUnit with IAsyncLifetime for async repository testing
- Distinctive: line 86
Ensure attribute scanning is stable and does not depend on nondeterministic ordering.
- Distinctive: line 87
Prefer explicit ordering or stable sorting (e.g., by full name) if scanning is used.
- Distinctive: line 91
If using reflection-based scanning for configuration discovery, constrain it to:
- Distinctive: line 229
If a PR adds a migration, verify it lands in the dedicated migrations project.
- Distinctive: line 230
If a PR changes entities, verify configuration is updated in the dedicated configuration type.
- Distinctive: line 231
If a new entity is added, verify it has:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Overview
Standardise Entity Framework Core usage with dedicated migrations projects excluded from code coverage, attribute-linked configuration types for entities, and deterministic configuration discovery. This ensures clean separation between business logic and infrastructure scaffolding.
When to Use
- Any solution using EF Core for persistence
- Adding or modifying DbContext, entity types, or configurations
- Creating or updating EF Core migrations
- Setting up code coverage exclusions for infrastructure projects
- Reviewing PRs that change persistence layer structure
Core Workflow
- Verify project structure: Ensure migrations are in a dedicated
*.Persistence.Migrationsproject - Configure coverage exclusion: Add
<ExcludeFromCodeCoverage>true</ExcludeFromCodeCoverage>to migrations project - Create configuration types: Implement dedicated
IEntityTypeConfiguration<T>for each entity - Link via attributes: Use attribute-based discovery to connect entities to their configurations
- Keep DbContext clean: DbContext applies configurations, not define them inline
- Ensure deterministic ordering: Configuration scanning uses stable ordering (e.g., by full name)
- Verify coverage gate: Run tests to confirm migrations project excluded from coverage reports
- Add repository tests: Use xUnit with
IAsyncLifetimefor async repository testing
Core
Defaults (non-negotiable)
1) Migrations project isolation
- EF Core migrations must be stored in a dedicated project (e.g.,
*.Persistence.Migrations). - The migrations project is treated as infrastructure output, not business logic.
Code coverage rule
- The migrations project must be excluded from code coverage (and from coverage gates), as it is generated/operational scaffolding rather than testable behaviour.
2) Dedicated type configuration types
- EF Core entity configuration must be implemented in dedicated configuration types
(one per entity/aggregate root as appropriate), not inline in
OnModelCreating. - DbContext should remain orchestration-only (apply configurations), not a monolithic configuration class.
3) Attribute-linked configuration discovery
- Configuration types must be linked by attribute to the entity they configure.
- The discovery mechanism (at startup) must apply configurations by scanning these attributes, ensuring:
- deterministic configuration application,
- clear traceability between entity and configuration type,
- avoidance of ad-hoc registration drift.
Review rules
- Reject PRs that add migrations to the main persistence project or domain project.
- Reject PRs that add entity configuration inline in
OnModelCreatingwhere a dedicated configuration type is expected. - Reject PRs where configuration types exist but are not discoverable via the attribute linking mechanism.
Load: examples
Recommended project layout
MyApp.Persistence(DbContext, repositories/infrastructure, runtime persistence wiring)MyApp.Persistence.Migrations(migrations only)MyApp.Domain(entities/value objects; no EF references where architecture requires)MyApp.Application(use cases; no EF references where architecture requires)
Coverage exclusion examples (tooling-agnostic intent)
- Exclude
MyApp.Persistence.Migrationsfrom coverage collection and thresholds. - Exclude generated migration snapshots.
Load: advanced
Deterministic configuration application
- Ensure attribute scanning is stable and does not depend on nondeterministic ordering.
- Prefer explicit ordering or stable sorting (e.g., by full name) if scanning is used.
Performance and trimming considerations
- If using reflection-based scanning for configuration discovery, constrain it to:
- a known assembly list,
- explicit type filters,
- and stable ordering.
- In AOT/trimming scenarios, ensure any reflection usage is compatible with trimming requirements (or provide a non-reflection fallback).
Load: coverage tooling
Coverlet configuration example
.runsettings (repository root):
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage" assemblyQualifiedName="Coverlet.Collector.DataCollection.CoverletInstrumentationCollector, coverlet.collector">
<Configuration>
<ExcludeByAttribute>CompilerGeneratedAttribute,GeneratedCodeAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
<ExcludeByFile>**/Migrations/**/*.cs</ExcludeByFile>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>Alternative: .csproj exclusion (for Migrations project):
<PropertyGroup>
<ExcludeFromCodeCoverage>true</ExcludeFromCodeCoverage>
</PropertyGroup>xUnit repository pattern examples
Repository interface with xUnit tests:
// Domain: IOrderRepository.cs
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
Task AddAsync(Order order, CancellationToken cancellationToken = default);
Task UpdateAsync(Order order, CancellationToken cancellationToken = default);
}
// Persistence: OrderRepository.cs
public class OrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public OrderRepository(AppDbContext context) => _context = context;
public Task<Order?> GetByIdAsync(int id, CancellationToken ct) =>
_context.Orders.FirstOrDefaultAsync(o => o.Id == id, ct);
public Task AddAsync(Order order, CancellationToken ct)
{
_context.Orders.Add(order);
return _context.SaveChangesAsync(ct);
}
public Task UpdateAsync(Order order, CancellationToken ct)
{
_context.Orders.Update(order);
return _context.SaveChangesAsync(ct);
}
}
// Tests: OrderRepositoryTests.cs
public class OrderRepositoryTests : IAsyncLifetime
{
private DbContextOptions<AppDbContext> _options = null!;
private AppDbContext _context = null!;
public async Task InitializeAsync()
{
_options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
_context = new AppDbContext(_options);
await _context.Database.EnsureCreatedAsync();
}
public async Task DisposeAsync()
{
await _context.DisposeAsync();
}
[Fact]
public async Task GetByIdAsync_WithValidId_ReturnsOrder()
{
// Arrange
var order = new Order { Id = 1, Status = OrderStatus.Pending };
_context.Orders.Add(order);
await _context.SaveChangesAsync();
var repository = new OrderRepository(_context);
// Act
var result = await repository.GetByIdAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Id);
}
[Fact]
public async Task AddAsync_WithNewOrder_PersistsSuccessfully()
{
// Arrange
var order = new Order { Status = OrderStatus.Pending };
var repository = new OrderRepository(_context);
// Act
await repository.AddAsync(order);
var retrieved = await _context.Orders.FindAsync(order.Id);
// Assert
Assert.NotNull(retrieved);
Assert.Equal(OrderStatus.Pending, retrieved.Status);
}
}Load: enforcement
Coverage gate
- Coverage enforcement must not fail due to migrations project changes.
- Any change to coverage config must keep the migrations project excluded.
Review heuristic: EF Core hygiene
- If a PR adds a migration, verify it lands in the dedicated migrations project.
- If a PR changes entities, verify configuration is updated in the dedicated configuration type.
- If a new entity is added, verify it has:
- a configuration type,
- an attribute link,
- and that discovery applies it.
Load: verification
Verification commands
1. Verify migrations project is excluded from coverage:
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencoverExpected output: Coverage report excludes *.Persistence.Migrations assembly.
2. Verify coverage threshold passes:
dotnet test /p:CollectCoverage=true /p:Threshold=75Expected: Build succeeds even if migrations project has low/zero coverage.
3. List excluded coverage paths:
# Inspect .runsettings to confirm ExcludeByFile patterns
cat .runsettingsExpected: File contains exclusion patterns for migrations path (e.g., **/Migrations/**/*.cs).
Verification checklist
- Migrations project has
<ExcludeFromCodeCoverage>true</ExcludeFromCodeCoverage>in.csprojOR excluded in.runsettings - Run
dotnet test /p:CollectCoverage=trueand verify migrations assembly does not appear in coverage report - xUnit repository tests use
IAsyncLifetimefor async setup/teardown - Configuration types exist in Persistence project (not in migrations)
- Migration files are in dedicated
*.Persistence.Migrationsproject only
Red Flags - STOP
These statements indicate EF Core hygiene issues:
| Thought | Reality |
| "Migrations can stay in the main project" | Isolate migrations in a dedicated project; they're scaffolding |
| "OnModelCreating is fine for all config" | Use dedicated configuration types per entity; keep DbContext clean |
| "Coverage should include migrations" | Exclude migrations from coverage; they're generated infrastructure |
| "Ad-hoc configuration is faster" | Attribute-linked discovery ensures consistency; prevents drift |
| "In-memory database is enough for tests" | Use TestContainers for realistic persistence testing when needed |
| "Reflection scanning order doesn't matter" | Ensure deterministic ordering; non-determinism causes subtle bugs |
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

