Lesson 35: Repository Pattern & Unit of Work
Abstract your data access with the Repository pattern and coordinate transactions with Unit of Work.
What You'll Learn
- • Generic Repository with CRUD operations
- • Specialized repositories for domain-specific queries
- • Unit of Work pattern for transactional consistency
- • Wiring everything together with Dependency Injection
🧠 Real-World Analogy
A Repository is like a librarian — you ask for books by criteria, and the librarian finds them without you needing to know which shelf they're on. The Unit of Work is like a shopping cart — you add multiple items, and they're all purchased (committed) together, or none at all if your card declines (rollback).
Generic & Specialized Repositories
A generic repository provides standard CRUD operations for any entity. Specialized repositories extend it with domain-specific queries (like "get recent orders" or "calculate total revenue"). This keeps your service layer clean and testable.
Generic & Specialized Repositories
Build a generic IRepository<T> and extend it with domain-specific OrderRepository.
using System;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
// Generic Repository Interface
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate);
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
}
// Generic Repository Implementation
public class Repository<T> : IRepository<T> where T : class
{
...Unit of Work
The Unit of Work pattern groups multiple repository operations into a single transaction. It holds references to all repositories and shares the same DbContext, ensuring SaveChanges() commits all changes atomically.
Unit of Work Pattern
Coordinate multiple repositories with transactional Unit of Work.
using System;
using Microsoft.EntityFrameworkCore;
// Unit of Work — coordinates multiple repositories
public interface IUnitOfWork : IDisposable
{
IRepository<Customer> Customers { get; }
IOrderRepository Orders { get; }
IRepository<Product> Products { get; }
Task<int> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitAsync();
Task RollbackAsync();
}
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
private IDbC
...Putting It Together
In your service layer, inject IUnitOfWork and use it to coordinate business operations. If placing an order involves checking stock, creating the order, and reducing inventory, all operations succeed or fail together.
OrderService Using Unit of Work
Place an order with atomic stock reduction and transaction management.
using System;
using Microsoft.Extensions.DependencyInjection;
// Service that uses Unit of Work
public class OrderService
{
private readonly IUnitOfWork _uow;
public OrderService(IUnitOfWork uow) => _uow = uow;
public async Task PlaceOrderAsync(int customerId, List<(int productId, int qty)> items)
{
await _uow.BeginTransactionAsync();
try
{
// Validate customer exists
var customer = await _uow.Customers.GetByIdAs
...Pro Tip
Some architects argue that EF Core's DbContext is already a Unit of Work and DbSet<T> is already a Repository. For simple apps, using DbContext directly is fine. Use Repository + UoW when you need testability, multiple data sources, or complex business transactions.
Common Mistakes
- • Creating a new DbContext per repository — they must share the same context
- • Exposing
IQueryablefrom repositories — leaks EF details to upper layers - • Forgetting to register repositories as Scoped — Transient creates separate contexts
Lesson Complete!
You've mastered Repository and Unit of Work patterns. Next, secure your APIs with JWT authentication and authorization.
Sign up for free to track which lessons you've completed and get learning reminders.