Link Search Menu Expand Document

Unit of Work


This page has the consolidated code of the Unit of Work reference implementation.

Interfaces

public interface IUnitOfWork<TDbConnection>
{
    TDbConnection Connection { get; }
    DbTransaction Transaction { get ; }
    void Begin();
    void Rollback();
    void Commit();
}

public interface IRepository<TEntity, TDbConnection>
    where TEntity : class
    where TDbConnection : IDbConnection
{
    void Attach(IUnitOfWork<TDbConnection> unitOfWork);
    TResult Save<TResult>(TEntity entity);
    int SaveAll(IEnumerable<TEntity> entities);
    int Delete(object id);
    TResult Merge<TResult>(TEntity entity);
    TEntity Query(object id);
    int Update(TEntity entity);
}

public interface IOrderRepository : IRepository<Order, SqlConnection>
{
}

public interface IOrderItemRepository : IRepository<OrderItem, SqlConnection>
{
}

public interface ISalesService
{
    void SaveOrders(Order order,
        IEnumerable<OrderItem> orderItems);
}

Classes

public class CustomUnitOfWork : IUnitOfWork<SqlConnection>
{
    private AppSettings _appSettings;
    private SqlConnection _connection;
    private DbTransaction _transaction;

    public CustomUnitOfWork(IOptions<AppSettings> options)
    {
        _appSettings = options.Value;
    }

    public SqlConnection Connection => _connection;

    public DbTransaction Transaction => _transaction;

    public void Begin()
    {
        if (_transaction != null)
        {
            throw new InvalidOperationException("Cannot start a new transaction while the existing one is still open.");
        }
        _connection = _connection ??= (SqlConnection)(new SqlConnection(_appSettings.ConnectionString)).EnsureOpen();
        _transaction = _connection.BeginTransaction();
    }

    public void Commit()
    {
        if (_transaction == null)
        {
            throw new InvalidOperationException("There is no active transaction to commit.");
        }
        using (_transaction)
        {
            _transaction.Commit();
        }
        _transaction = null;
    }

    public void Rollback()
    {
        if (_transaction == null)
        {
            throw new InvalidOperationException("There is no active transaction to rollback.");
        }
        using (_transaction)
        {
            _transaction.Rollback();
        }
        _transaction = null;
    }
}

Repositories

public class EntityRepository<TEntity> : BaseRepository<TEntity, SqlConnection>,
    IRepository<TEntity, SqlConnection>
    where TEntity : class
{
    private IUnitOfWork<SqlConnection> _unitOfWork;

    public EntityRepository(IOptions<AppSettings> options)
        : base(options.Value.ConnectionString) { }

    public void Attach(IUnitOfWork<SqlConnection> unitOfWork) =>
        _unitOfWork = unitOfWork;

    public TResult Save<TResult>(TEntity entity) =>
        Insert<TResult>(entity,
            transaction: _unitOfWork.Transaction);

    public int SaveAll(IEnumerable<TEntity> entities) =>
        InsertAll(entities,
            transaction: _unitOfWork.Transaction);

    public int Delete(object id) =>
        Delete(id,
            transaction: _unitOfWork.Transaction);

    public TResult Merge<TResult>(TEntity entity) =>
        Merge<TResult>(entity,
            transaction: _unitOfWork.Transaction);

    public TEntity Query(object id) =>
        Query(id,
            transaction: _unitOfWork.Transaction)?.FirstOrDefault();

    public int Update(TEntity entity) =>
        Update(entity,
            transaction: _unitOfWork.Transaction);
}

public class OrderRepository : EntityRepository<Order>, IOrderRepository
{
    public OrderRepository(IOptions<AppSettings> options)
        : base(options)
    { }
}

public class OrderItemRepository : EntityRepository<OrderItem>, IOrderItemRepository
{
    public OrderItemRepository(IOptions<AppSettings> options)
        : base(options)
    { }
}

Services

public class SalesService : ISalesService
{
    private IUnitOfWork<SqlConnection> _unitOfWork;
    private IOrderRepository _orderRepository;
    private IOrderItemRepository _orderItemRepository;

    public SalesService(IUnitOfWork<SqlConnection> unitOfWork,
        IOrderRepository orderRepository,
        IOrderItemRepository orderItemRepository
        /* Other repositories here */)
    {
        _unitOfWork = unitOfWork;
        _orderRepository = orderRepository;
        _orderItemRepository = orderItemRepository;

        // Attach the UOW
        _orderRepository.Attach(_unitOfWork);
        _orderItemRepository.Attach(_unitOfWork);
    }

    public void SaveOrders(Order order,
        IEnumerable<OrderItem> orderItems)
    {
        // Start the UOW
        _unitOfWork.Begin();

        try
        {
            // Call the repository methods
            var orderId = _orderRepository.Save<int>(order);
            orderItems
                .AsList()
                .ForEach(e => e.OrderId = orderId);
            _orderItemRepository.SaveAll(orderItems);

            // Commit
            _unitOfWork.Commit();
        }
        catch
        {
            // Rollback
            _unitOfWork.Rollback();
        }
    }
}

Dependency Injection

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    // Unit Of Work
    services.AddTransient<IUnitOfWork, CustomUnitOfWork>();

    // Repositories
    services.AddSingleton<IOrderRepository, OrderRepository>();
    services.AddSingleton<IOrderItemRepository, OrderItemRepository>();
    /* Do the same for the other repositories */

    // Business Logics
    services.AddTransient<ISalesManager, SalesManager>();
}