Repository hides data-access logic behind an interface, so business logic depends on that abstraction instead of directly on a specific persistence technology — this is the pattern the Dependency Inversion example earlier in this reference was already demonstrating.
public interface IOrderRepository
{
Order? GetById(int id);
void Add(Order order);
}
public class EfOrderRepository : IOrderRepository
{
private readonly AppDbContext _context;
public EfOrderRepository(AppDbContext context) => _context = context;
public Order? GetById(int id) => _context.Orders.Find(id);
public void Add(Order order) => _context.Orders.Add(order);
}
The direct payoff: OrderService (or any class depending on IOrderRepository) can be unit tested against a fake in-memory implementation with zero real database involved, and the concrete persistence technology (Entity Framework here, but it could be Dapper, MongoDB, or a REST call to another service) can be swapped without touching business logic at all.
Unit of Work tracks a set of changes across possibly multiple repositories and commits them together as a single transaction — solving the problem of a caller needing to update an order and decrement inventory and log an audit entry, all as one atomic operation, even though each of those might be handled by a different repository.
public interface IUnitOfWork
{
IOrderRepository Orders { get; }
IInventoryRepository Inventory { get; }
Task<int> SaveChangesAsync();
}
public class EfUnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;
public IOrderRepository Orders { get; }
public IInventoryRepository Inventory { get; }
public EfUnitOfWork(AppDbContext context)
{
_context = context;
Orders = new EfOrderRepository(context);
Inventory = new EfInventoryRepository(context);
}
public Task<int> SaveChangesAsync() => _context.SaveChangesAsync(); // one commit for every tracked change
}
// Both changes are staged, then committed together in one database transaction.
async Task PlaceOrderAsync(IUnitOfWork uow, Order order)
{
uow.Orders.Add(order);
uow.Inventory.Decrement(order.ProductId, order.Quantity);
await uow.SaveChangesAsync(); // Atomicity, from the ACID section, delivered here
}
In .NET, EF Core's DbContext already functions as a Unit of Work internally — every tracked entity change across every DbSet it owns is committed together by one SaveChanges() call — which is exactly why application code calls SaveChanges() once at the end of a business operation, rather than after every individual property change.