Previous Repository pattern NoSQL Integration (MongoDB, Redis) Next

🧩 Unit of Work Pattern in .NET Core

📖 What is the Unit of Work Pattern?

The Unit of Work (UoW) pattern is a design pattern that coordinates the work of multiple repositories by treating a set of operations as a single transaction. It ensures that either all operations succeed or none of them are committed, maintaining data consistency.

🛠 Example in .NET Core with EF Core

1. Define Repositories

public interface IProductRepository
{
    Task<IEnumerable<Product>> GetAllAsync();
    Task AddAsync(Product product);
}

public interface IOrderRepository
{
    Task<IEnumerable<Order>> GetAllAsync();
    Task AddAsync(Order order);
}

    

2. Implement Repositories

        public class ProductRepository : IProductRepository
        {
        private readonly AppDbContext _context;
        public ProductRepository(AppDbContext context) => _context = context;

        public async Task<IEnumerable<Product>> GetAllAsync() => await _context.Products.ToListAsync();
        public async Task AddAsync(Product product) => await _context.Products.AddAsync(product);
        }

        public class OrderRepository : IOrderRepository
        {
        private readonly AppDbContext _context;
        public OrderRepository(AppDbContext context) => _context = context;

        public async Task<IEnumerable<Order>> GetAllAsync() => await _context.Orders.ToListAsync();
        public async Task AddAsync(Order order) => await _context.Orders.AddAsync(order);
        }
        

3. Define Unit of Work Interface

public interface IUnitOfWork : IDisposable
{
    IProductRepository Products { get; }
    IOrderRepository Orders { get; }
    Task<int> CompleteAsync();
}
    

4. Implement Unit of Work

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;
    public IProductRepository Products { get; }
    public IOrderRepository Orders { get; }

    public UnitOfWork(AppDbContext context, IProductRepository products, IOrderRepository orders)
    {
        _context = context;
        Products = products;
        Orders = orders;
    }

    public async Task<int> CompleteAsync() => await _context.SaveChangesAsync();

    public void Dispose() => _context.Dispose();
}
    

5. Usage in Business Logic

public class OrderService
{
    private readonly IUnitOfWork _unitOfWork;
    public OrderService(IUnitOfWork unitOfWork) => _unitOfWork = unitOfWork;

    public async Task PlaceOrder(Order order, Product product)
    {
        await _unitOfWork.Products.AddAsync(product);
        await _unitOfWork.Orders.AddAsync(order);
        await _unitOfWork.CompleteAsync(); // commits both in one transaction
    }
}
    

✅ Advantages

  • Ensures data consistency across multiple repositories.
  • Centralizes transaction management.
  • Improves maintainability and testability.
  • Works well with Repository Pattern.

⚠️ Disadvantages

  • Extra abstraction layer adds complexity.
  • EF Core’s DbContext already acts like a Unit of Work, so it can be redundant.
  • Overkill for small/simple applications.

🧭 Best Practices

  • Use UoW with Repository Pattern in complex domains.
  • Keep UoW implementation thin — delegate to DbContext.
  • Inject UoW via Dependency Injection for testability.
  • Combine with transactions for critical operations.

🔒 Precautions

  • Don’t misuse UoW for trivial apps — DbContext may be enough.
  • Ensure async/await is used to avoid blocking calls.
  • Dispose UoW properly to release DbContext resources.
  • Handle exceptions to avoid partial commits.

🎯 Summary

The Unit of Work Pattern is a powerful way to manage transactions across multiple repositories in .NET Core. While EF Core’s DbContext already provides UoW-like behavior, implementing it explicitly can improve clarity, maintainability, and testability in larger projects.

⚖️ Repository vs Unit of Work vs Specification Pattern

📊 Comparison Table

Aspect Repository Pattern Unit of Work Pattern Specification Pattern
Definition Abstracts data access logic, providing a collection-like interface for entities. Coordinates multiple repositories and ensures all changes are committed as a single transaction. Encapsulates query logic into reusable, composable specifications.
Main Purpose Decouple business logic from persistence logic. Maintain consistency across multiple operations. Centralize and reuse complex query logic.
When to Use Anytime you want clean separation of concerns and testability. When multiple repositories need to be updated in a single transaction. When queries are complex, repeated, or need to be combined dynamically.
Advantages Improves maintainability, testability, and readability. Ensures atomic commits, reduces risk of partial updates. Promotes DRY principle, improves readability of queries.
Disadvantages May duplicate EF Core’s DbSet functionality. EF Core’s DbContext already acts like a UoW, so can be redundant. Extra abstraction layer, may be overkill for simple queries.
Example in .NET Core _repository.Add(product); await _unitOfWork.CompleteAsync(); var spec = new ProductsByCategorySpec("Electronics");

🎯 Summary

- The Repository Pattern abstracts persistence logic. - The Unit of Work Pattern ensures multiple repository operations are committed as one transaction. - The Specification Pattern encapsulates query logic for reuse and composition. Together, they form a powerful trio for building clean, testable, and maintainable architectures in .NET Core.

Back to Index
Previous Repository pattern NoSQL Integration (MongoDB, Redis) Next
*