0
0
CsharpConceptBeginner · 4 min read

Unit of Work Pattern in C#: What It Is and How It Works

The Unit of Work pattern in C# is a design pattern that keeps track of changes to objects during a business transaction and coordinates the writing of those changes to the database as a single unit. It helps manage complex data operations by grouping them into one transaction, ensuring consistency and reducing errors.
⚙️

How It Works

Imagine you are writing a shopping list and want to make sure you only go to the store once to buy everything. The Unit of Work pattern works similarly by collecting all changes you want to make to your data, like adding, updating, or deleting items, and then saving them all at once. This way, if something goes wrong, you can cancel the whole operation instead of ending up with partial changes.

In C#, the Unit of Work acts like a manager that tracks all the changes made to objects during a session. When you call its save method, it sends all those changes to the database in one go. This helps keep your data consistent and avoids problems like saving half-done updates.

💻

Example

This example shows a simple Unit of Work managing two repositories and saving changes together.
csharp
using System;
using System.Collections.Generic;

// Simple entity
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// Repository interface
public interface IRepository<T>
{
    void Add(T entity);
    void Remove(T entity);
    IEnumerable<T> GetAll();
}

// In-memory repository implementation
public class ProductRepository : IRepository<Product>
{
    private readonly List<Product> _products = new();

    public void Add(Product entity) => _products.Add(entity);
    public void Remove(Product entity) => _products.Remove(entity);
    public IEnumerable<Product> GetAll() => _products;
}

// Unit of Work interface
public interface IUnitOfWork : IDisposable
{
    IRepository<Product> Products { get; }
    void Commit();
}

// Unit of Work implementation
public class UnitOfWork : IUnitOfWork
{
    private readonly ProductRepository _productRepository = new();
    private readonly List<Action> _operations = new();

    public IRepository<Product> Products => new RepositoryProxy(_productRepository, _operations);

    public void Commit()
    {
        foreach (var operation in _operations)
        {
            operation();
        }
        _operations.Clear();
        Console.WriteLine("All changes committed.");
    }

    public void Dispose() { }

    // Proxy to delay operations until Commit
    private class RepositoryProxy : IRepository<Product>
    {
        private readonly ProductRepository _repo;
        private readonly List<Action> _ops;

        public RepositoryProxy(ProductRepository repo, List<Action> ops)
        {
            _repo = repo;
            _ops = ops;
        }

        public void Add(Product entity)
        {
            _ops.Add(() => _repo.Add(entity));
        }

        public void Remove(Product entity)
        {
            _ops.Add(() => _repo.Remove(entity));
        }

        public IEnumerable<Product> GetAll() => _repo.GetAll();
    }
}

class Program
{
    static void Main()
    {
        using var uow = new UnitOfWork();

        uow.Products.Add(new Product { Id = 1, Name = "Apple" });
        uow.Products.Add(new Product { Id = 2, Name = "Banana" });

        Console.WriteLine("Before commit:");
        foreach (var p in uow.Products.GetAll())
        {
            Console.WriteLine(p.Name);
        }

        uow.Commit();

        Console.WriteLine("After commit:");
        foreach (var p in uow.Products.GetAll())
        {
            Console.WriteLine(p.Name);
        }
    }
}
Output
Before commit: After commit: Apple Banana All changes committed.
🎯

When to Use

Use the Unit of Work pattern when your application needs to handle multiple changes to data that must be saved together to keep everything correct. For example, in an online store, when a customer places an order, you might update the order details, reduce stock, and record payment all at once. The Unit of Work ensures these steps happen together or not at all.

This pattern is especially helpful when working with databases through Object-Relational Mappers (ORMs) like Entity Framework, where it manages transactions and reduces the risk of data inconsistency.

Key Points

  • Unit of Work groups multiple data changes into a single transaction.
  • It tracks changes and delays saving until explicitly committed.
  • Helps maintain data consistency and avoid partial updates.
  • Commonly used with repositories and ORMs in C# applications.

Key Takeaways

Unit of Work manages multiple data changes as one transaction to keep data consistent.
It tracks changes and commits them together, reducing errors and partial saves.
Ideal for complex operations involving multiple related data updates.
Works well with repository patterns and ORMs like Entity Framework.
Helps simplify transaction management in C# applications.