RCommon
HomeGitHub
  • Introduction
  • Common Scenarios
  • Getting Started
    • Running Examples
    • Roadmap
    • Releases
      • 1.0.1.75
      • 1.0.2.0
      • 2.0.0
      • 2.1.0
  • Topics
    • Fundamentals
      • Configuration
      • Logging
      • GUID Generation
      • Time and Date
      • Emailing
        • SMTP Email
        • SendGrid Email API
      • Data Transfer Objects
        • Pagination
      • Security
        • Current User
        • Claims
      • Events
        • Transactional Events
        • Synchronous Events
        • Asynchronous Events
        • Producers
        • Subscribers
      • Validation
        • Fluent Validation
      • Caching
        • Dynamically Compiled Expressions
        • Persistence Caching
        • Caching Services
        • Redis & Valkey
        • Memory Cache
      • Serialization
        • JSON.NET
        • System.Text.Json
    • Patterns
      • Specification
      • Mediator
        • MediatR
          • Validator Behavior
          • Unit of Work Behavior
          • Logging Behavior
      • CQRS
        • Commands
        • Queries
      • Persistence
        • Repository
          • Entity Framework Core
          • Dapper
          • Linq2Db
        • Transactions
          • Unit of Work
      • Event Bus
        • In Memory
        • MediatR
        • Wolverine
      • Message Bus
        • MassTransit
        • Wolverine
    • Architecture
      • Overview
      • Microservices
      • Clean Architecture
      • Event Driven Architecture
  • Examples
    • Clean Architecture
    • CQRS
    • Mediator: MediatR
    • Event Handling: In Memory
    • Event Handling: MediatR
    • Event Handling: MassTransit
    • Event Handling: Wolverine
    • Validation: Fluent Validation
Powered by GitBook
On this page
  1. Topics
  2. Patterns
  3. Persistence
  4. Transactions

Unit of Work

Unit of work pattern used within RCommon.

PreviousTransactionsNextEvent Bus

Last updated 1 year ago

You can use the unit of work pattern in your code by using the UnitOfWorkScope interface acquired by using the UnitOfWorkScopeFactory.

var customer = new Customer { FirstName = "John", LastName = "Doe" };
var salesPerson = new SalesPerson { FirstName = "Jane", LastName = "Doe", SalesQuota = 2000 };

// Setup required services
var scopeFactory = this.ServiceProvider.GetService<IUnitOfWorkScopeFactory>();

using (var scope = scopeFactory.Create(TransactionMode.Default))
{
    var customerRepo = this.ServiceProvider.GetService<IFullFeaturedRepository<Customer>>();
    repo.DataStoreName = "TestDbContext";
    await customerRepo.AddAsync(customer);
    var salesPersonRepo = this.ServiceProvider.GetService<IFullFeaturedRepository<SalesPerson>>();
    salesPersonRepo.DataStoreName = "TestDbContext";
    await salesPersonRepo.AddAsync(salesPerson);
    scope.Commit();
}

Any repository implemented in RCommon implicitly uses the Unit of Work pattern in a loosely coupled manner. The UnitOfWorkScope class essentially wraps so all the rules of using TransactionScope still apply.

Unit of Work in MediatR Behavior Pipeline

In addition to being able to use the UnitOfWorkScope procedurally in your applications, you can also enlist the Unit of Work has a behavior in the MediatR pipeline:

public class UnitOfWorkBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
{
    private readonly ILogger<UnitOfWorkBehavior<TRequest, TResponse>> _logger;
    private readonly IUnitOfWorkScopeFactory _unitOfWorkScopeFactory;
    private readonly IUnitOfWorkManager _unitOfWorkManager;

    public UnitOfWorkBehavior(IUnitOfWorkScopeFactory unitOfWorkScopeFactory, IUnitOfWorkManager unitOfWorkManager,
        ILogger<UnitOfWorkBehavior<TRequest, TResponse>> logger)
    {
        _unitOfWorkScopeFactory = unitOfWorkScopeFactory ?? throw new ArgumentException(nameof(IUnitOfWorkScopeFactory));
        _unitOfWorkManager = unitOfWorkManager  ?? throw new ArgumentException(nameof(IUnitOfWorkManager)); 
        _logger = logger ?? throw new ArgumentException(nameof(ILogger));
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var response = default(TResponse);
        var typeName = request.GetGenericTypeName();

        try
        {
            using (var unitOfWork = this._unitOfWorkScopeFactory.Create(TransactionMode.Default))
            {
                _logger.LogInformation("----- Begin transaction {UnitOfWorkTransactionId} for {CommandName} ({@Command})", 
                    this._unitOfWorkManager.CurrentUnitOfWork.TransactionId, typeName, request);

                response = await next(); // Business logic and peristence happens here

                _logger.LogInformation("----- Commit transaction {UnitOfWorkTransactionId} for {CommandName}", 
                    this._unitOfWorkManager.CurrentUnitOfWork.TransactionId, typeName);

                unitOfWork.Commit();
            }
            

            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ERROR Handling transaction for {CommandName} ({@Command})", typeName, request);

            throw;
        }
    }
}

...and is configured as:

ConfigureRCommon.Using(new DotNetCoreContainerAdapter(builder.Services))
    .WithStateStorage<DefaultStateStorageConfiguration>()
        x.WithUnitOfWork<DefaultUnitOfWorkConfiguration>()
    )
    .AddUnitOfWorkToMediatorPipeline() // Add Unit of Work to Mediator Pipeline
    .WithPersistence<EFCoreConfiguration>(x =>
        x.UsingDbContext<LeaveManagementDbContext>());
TransactionScope