C# Architecture Mastery — EF Core in Clean Architecture (Part 8)
Source: Dev.to
EF Core’s Role in Clean Architecture
-
What EF Core is
- A persistence mechanism
- A data‑mapping tool
- An infrastructure concern
-
What EF Core is not
- A domain model
- A business‑rule engine
- An architectural foundation
If EF Core leaks inward, Clean Architecture collapses.
Where EF Core Lives
Infrastructure
└─ Persistence
├─ DbContext
├─ EntityConfigurations
└─ Repositories
The Domain and Application layers must never reference:
DbContextDbSet- EF Core attributes
- EF Core LINQ extensions
The Hard Boundary: No DbContext Leakage
// ❌ DbContext leaking inward
class CreateOrderUseCase
{
private readonly AppDbContext _db;
}
Consequences
- Tight coupling
- Persistence‑aware business logic
- Un‑testable use cases
Depend on Interfaces, Not EF Core
public interface IOrderRepository
{
Task SaveAsync(Order order);
Task GetByIdAsync(OrderId id);
}
Infrastructure Implementation
class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _db;
public async Task SaveAsync(Order order)
{
_db.Orders.Add(order);
await _db.SaveChangesAsync();
}
}
EF Core stays isolated behind the repository abstraction.
Senior Guideline: Let the Domain Drive EF Core
-
Avoid
- Anemic entities shaped for EF
- Public setters everywhere
- Persistence‑driven invariants
-
Prefer
- Rich domain models
- Encapsulation
- Explicit invariants
EF Core can map to private fields and constructors.
Common Mistake: Leaking IQueryable Upward
// ❌ IQueryable leaking upward
IQueryable Orders { get; }
Why It’s Dangerous
- Couples logic to EF query translation
- Breaks abstraction
- Pushes business rules into queries
Correct Approach
- Keep LINQ inside repositories
- Return domain objects or DTOs
Scenarios Where EF Core Is Not Ideal
- Complex reporting queries
- High‑performance read models
- Bulk operations
- Heavy analytics
Senior teams often combine:
- EF Core for writes and aggregates
- Dapper / raw SQL for reads
This hybrid approach is perfectly acceptable.
What EF Core Handles Well
- Migrations
- Change tracking
- Transactions
The Unit of Work concept, however, belongs to the application layer:
public interface IUnitOfWork
{
Task CommitAsync();
}
EF Core provides an implementation but does not define the abstraction.
Testing Guidelines
- Do: Write integration tests with real providers; validate mappings and constraints.
- Avoid: Mocking
DbContextor overusing the InMemory provider for logic tests.
If EF Core is hard to test, the boundaries are likely misplaced.
Warning Signs (Boundary Violations)
DbContextinjected into controllers or domain services- EF attributes on domain entities
- Business rules embedded in LINQ queries
- Heavy reliance on lazy loading
Each smell indicates a breach of the architectural boundary.
Summary
- Correct usage: EF Core disappears behind abstractions, supports the domain, and remains replaceable.
- Incorrect usage: EF Core becomes the system, dictates design, and blocks evolution.
In Clean Architecture, EF Core must serve the domain—never rule it.
Written by Cristian Sifuentes — helping teams tame EF Core so architecture stays clean, testable, and resilient.