Introduction
Article 19 introduced Repository + Unit of Work — this deep dive adds Specification pattern, read-only repos, async throughout, and honest guidance on when EF Core makes generic repos unnecessary.
After this article you will
- Implement IRepository<T> with specifications
- Build read-only query repositories
- Coordinate with Unit of Work
- Compare EF vs Dapper repository implementations
- Know when NOT to use repository over DbContext
Prerequisites
- Article 45 — CQRS with MediatR
- ShopNest API, EF Core, and DI from prior modules
Concept deep-dive
public interface ISpecification<T>
{
Expression<Func<T, bool>>? Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
}
public class CustomerByRegionSpec : Specification<Customer>
{
public CustomerByRegionSpec(string region) =>
AddCriteria(c => c.Region == region);
}
public async Task<IReadOnlyList<Customer>> ListAsync(ISpecification<Customer> spec)
{
return await ApplySpecification(_db.Customers, spec).ToListAsync();
}
When NOT to use: Simple apps where DbContext + specifications or direct LINQ is enough — avoid IRepository<T> that only wraps DbSet with no added value.
Hands-on — ShopNest CRM System
- ICustomerRepository with GetById, List(spec), Add.
- CustomerByRegionSpec + active-only spec.
- UnitOfWork.SaveChangesAsync in CRM lead conversion flow.
- Mock repository in CRM service unit test.
Common errors & best practices
- Leaking IQueryable from repository — callers compose unbounded queries.
- Generic repo returning IQueryable — prefer specifications or dedicated query methods.
Interview questions
Q: Specification pattern?
A: Encapsulates query logic reusable and testable without duplicating Where/Include chains.
Q: Repository over EF antipattern?
A: Debated — valuable for test seams and complex domains; redundant for trivial apps.
Summary
- Specifications encapsulate query intent for CRM
- Read-only repos prevent accidental writes on reports
- Unit of Work batches CRM transaction commits
- Use repositories when they add testability or abstraction
Previous: CQRS with MediatR
Next: Background Services and Hosted Services
FAQ
Dapper repository?
Implement same ICustomerRepository — swap in DI for read-heavy reports.
Generic IRepository<T>?
OK for CRUD entities; add specific repos for complex aggregates.