When building enterprise-grade .NET applications, managing data consistency, transactions, and multiple repository calls can become messy fast. Imagine saving an Order and its OrderItems, then updating the Inventory — if one step fails, you don’t want your database to be half-updated.

That’s where the Unit of Work Pattern comes in.

In this blog, we’ll explore,

What is the Unit of Work Pattern?

The Unit of Work is a design pattern that.

Think of it as a transaction manager that coordinates multiple repositories behind the scenes.

Why Use Unit of Work?

How to Implement Unit of Work in .NET?

Let’s build it step by step.

1. Define the Repository Interfaces

Example for a generic repository.

public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    void Update(T entity);
    void Delete(T entity);
}

2. Implement the Repositories

Example with EF Core.

public class Repository<T> : IRepository<T> where T : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;

    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    public async Task<T> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
    public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
    public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
    public void Update(T entity) => _dbSet.Update(entity);
    public void Delete(T entity) => _dbSet.Remove(entity);
}

Create the Unit of Work Interface.

public interface IUnitOfWork : IDisposable
{
    IRepository<Order> Orders { get; }
    IRepository<Customer> Customers { get; }
    Task<int> CompleteAsync();
}

Implement the Unit of Work.

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;
    public IRepository<Order> Orders { get; }
    public IRepository<Customer> Customers { get; }

    public UnitOfWork(AppDbContext context)
    {
        _context = context;
        Orders = new Repository<Order>(_context);
        Customers = new Repository<Customer>(_context);
    }

    public async Task<int> CompleteAsync()
    {
        return await _context.SaveChangesAsync();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Usage Example

public class OrderService
{
    private readonly IUnitOfWork _unitOfWork;

    public OrderService(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task PlaceOrderAsync(Order order)
    {
        await _unitOfWork.Orders.AddAsync(order);
        // Maybe update Inventory
        // _unitOfWork.Inventory.Update(stock);

        await _unitOfWork.CompleteAsync();
    }
}

Benefits of a Unit of Work

Best Practices

Combine with Generic Repository for maximum reuse.

Conclusion

The Unit of Work Pattern is a must-have tool in your .NET toolbox for building reliable, maintainable, and testable applications. It’s the glue that ensures all your repositories work together seamlessly, giving you confidence that your data stays consistent no matter what happens.