Introduction
The Repository + Unit of Work pattern wraps EF Core behind interfaces so ShopNest's shopping cart logic is testable with mocks and controllers stay thin. Teams debate whether it's essential — this lesson shows both sides with a working cart.
After this article you will
- Implement IRepository<T> and specific repositories
- Coordinate saves with IUnitOfWork
- Register repositories in DI
- Unit test CartService with mocked IProductRepository
- Evaluate repository vs direct DbContext trade-offs
Prerequisites
- Article 18 — EF Core Fluent API
- ShopNest DbContext with Products/Orders from Articles 13–15
- SQL Server or LocalDB configured
Concept deep-dive
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id, CancellationToken ct = default);
Task AddAsync(T entity, CancellationToken ct = default);
void Update(T entity);
void Remove(T entity);
}
public interface IUnitOfWork : IAsyncDisposable
{
IProductRepository Products { get; }
ICartRepository Carts { get; }
Task<int> SaveChangesAsync(CancellationToken ct = default);
}
public class UnitOfWork : IUnitOfWork
{
private readonly ShopNestDbContext _db;
public UnitOfWork(ShopNestDbContext db) => _db = db;
public IProductRepository Products => new ProductRepository(_db);
public ICartRepository Carts => new CartRepository(_db);
public Task<int> SaveChangesAsync(CancellationToken ct = default)
=> _db.SaveChangesAsync(ct);
}
Debate: EF Core DbContext already implements Unit of Work + Repository. Extra layer adds boilerplate but helps when swapping data access in tests or multi-database scenarios. Microsoft docs: often skip generic repository for simple apps.
Hands-on — ShopNest Online Shopping Cart
- ICartRepository with GetOrCreateCartAsync(userId).
- CartService.AddItemAsync — uses IUnitOfWork, not DbContext directly.
- xUnit test: mock IProductRepository returns product; verify AddAsync called.
- Register:
services.AddScoped<IUnitOfWork, UnitOfWork>();
Common errors & best practices
- Repository wrapping every DbSet with no added value — prefer specific repos with domain queries.
- Singleton UnitOfWork — must be Scoped per request.
Interview questions
Q: Is Repository an antipattern over EF?
A: Generic repo over DbSet adds little; specific repos with domain queries still help testability in large teams.
Q: Unit of Work purpose?
A: Single SaveChanges across multiple repos in one transaction boundary.
Summary
- Repository abstracts data access for testability
- Unit of Work shares one DbContext per request
- Shopping cart demo ties products, cart lines, SaveChanges
- Skip generic repo if team prefers DbContext + mock in tests
Previous: EF Core Fluent API
Next: EF Core Performance Optimization
FAQ
Should ShopNest use Repository everywhere?
Start with DbContext in small modules; add repos when testing or swapping storage becomes painful.
How mock in tests?
Moq IProductRepository — no in-memory DB required for pure service tests.