Entity Framework Core Patterns
When to Use This Skill
Use this skill when:
- Setting up EF Core in a new project
- Optimizing query performance
- Managing database migrations
- Integrating EF Core with .NET Aspire
- Debugging change tracking issues
- Loading multiple navigation collections efficiently (query splitting)
Core Principles
- NoTracking by Default - Most queries are read-only; opt-in to tracking
- Never Edit Migrations Manually - Always use CLI commands
- Dedicated Migration Service - Separate migration execution from application startup
- ExecutionStrategy for Retries - Handle transient database failures
- Explicit Updates - When NoTracking, explicitly mark entities for update
Pattern 1: NoTracking by Default
Configure your DbContext to disable change tracking by default. This improves performance for read-heavy workloads.
When NoTracking is Active
Read-only queries work normally:
Writes require explicit handling:
When to Use Tracking
Explicit Add/Update Pattern
Pattern 2: Never Edit Migrations Manually
CRITICAL: Always use EF Core CLI commands to manage migrations. Never:
- Manually edit migration files (except for custom SQL in
Up()/Down()) - Delete migration files directly
- Rename migration files
- Copy migrations between projects
Creating Migrations
Removing Migrations
Applying Migrations
Generating SQL Scripts
Pattern 3: Dedicated Migration Service with Aspire
Separate migration execution from your main application using a dedicated migration service. This ensures:
- Migrations complete before the app starts
- Clean separation of concerns
- Controlled seeding in test environments
Project Structure
MigrationService Program.cs
MigrationWorker.cs
AppHost Configuration
Pattern 4: ExecutionStrategy for Transient Failures
Always use CreateExecutionStrategy() for operations that might fail transiently:
Important: You cannot use CreateExecutionStrategy() with user-initiated transactions. If you need transactions with retry:
Pattern 5: Bulk Operations with ExecuteUpdate/ExecuteDelete
For bulk operations, use EF Core 7+ ExecuteUpdateAsync and ExecuteDeleteAsync instead of loading entities:
Common Pitfalls
1. Forgetting to Update When NoTracking
2. N+1 Query Problem
3. Tracking Conflicts with Multiple DbContext Instances
4. Not Using Async Consistently
5. Querying Inside Loops
DbContext Lifetime in DI
ASP.NET Core (Scoped by Default)
Background Services (Create Scope)
Actors / Long-Lived Objects (Factory Pattern)
Pattern 6: Query Splitting to Prevent Cartesian Explosion
When you load multiple navigation collections via Include(), EF Core generates a single query that can cause cartesian explosion. If you have 10 orders with 10 items each, you get 100 rows instead of 10 + 10.
Global Configuration (Recommended for Most Cases)
Enable query splitting globally in your DbContext configuration:
Per-Query Override
Use single query when you know it's more efficient:
Trade-offs
Recommendation: Default to SplitQuery globally, override with AsSingleQuery() for specific queries where single-query is known to be better.
When to Prefer SingleQuery
- Small, well-understood navigation graphs (2-3 levels)
- Queries where all related data is always needed
- Performance-critical paths where round-trip cost is lower than cartesian explosion
When to Prefer SplitQuery
- Large or unpredictable navigation graphs
- Many-to-many relationships
- Queries loading collections that may not all be needed
Testing with EF Core
In-Memory Provider (Unit Tests Only)
Real Database with TestContainers (Integration Tests)
See the testcontainers-integration-tests skill for proper database testing.


