Repository & Unit of Work: Decoupling Data Access
Repository & Unit of Work: Decoupling Data Access
The Repository Pattern is a structural pattern that decouples the data access layer from the business logic layer. The Unit of Work Pattern coordinates the work of multiple repositories by creating a single database context shared by all of them.
🏗️ The Problem
If your business logic (Services/Controllers) contains direct database calls (e.g., using _context.Users.Add(user)), it becomes hard to test and maintain. If you change your ORM (e.g., switch from EF Core to Dapper), you have to change your business logic in many places.
🚀 The .NET Implementation
This is one of the most common architecture patterns in modern .NET applications.
1. The Repository Interface
public interface IUserRepository
{
Task<User?> GetByIdAsync(int id);
Task AddAsync(User user);
void Update(User user);
}2. The Concrete Repository (using EF Core)
public class UserRepository : IUserRepository
{
private readonly MyDbContext _context;
public UserRepository(MyDbContext context)
{
_context = context;
}
public async Task<User?> GetByIdAsync(int id) => await _context.Users.FindAsync(id);
public async Task AddAsync(User user) => await _context.Users.AddAsync(user);
public void Update(User user) => _context.Users.Update(user);
}3. The Unit of Work (The Manager)
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
Task<int> CompleteAsync(); // SaveChanges in one transaction
}
public class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
public IUserRepository Users { get; private set; }
public UnitOfWork(MyDbContext context)
{
_context = context;
Users = new UserRepository(_context);
}
public async Task<int> CompleteAsync() => await _context.SaveChangesAsync();
}🛠️ Real-World Usage (Client Service)
public class UserService
{
private readonly IUnitOfWork _uow;
public UserService(IUnitOfWork uow)
{
_uow = uow;
}
public async Task CreateUserAsync(User user)
{
await _uow.Users.AddAsync(user);
// Add more operations here if needed
await _uow.CompleteAsync(); // All changes are saved together!
}
}💡 Why use Repository & UoW?
- Testability: You can easily mock the
IUnitOfWorkfor unit testing your services without a database. - Maintainability: All data access logic is in one place.
- Transaction Management: Unit of Work ensures that multiple changes are saved in a single transaction (all or nothing).