Две команды cqrs обернуть в транзакцию

Использую библиотеку mediatr и мне надо обернуть две команды в одну транзакцию.

В качестве примера: есть таблица Persons и для каждого человека при редактировании в отдельную таблицу должно предварительно сохраняться текущее содержимое полей. Естественно, это просится обернуть в единую транзакцию, чтобы сначала сохранились текущие поля, а потом уже произошло обновление.

Текущий вариант выглядит так:

public class Command : IRequest<PersonData>
{
    public PersonData Person { get; set; }
}

Обработка в Handler просто записывает в базу:

public class Handler : BaseHandler, IRequestHandler<Command, PersonData>
{
    private readonly IRequestHandler<PersonEdits.Create.Command, bool> _createPersonEditsHandler;

    public Handler(ApplicationDbContext applicationDbContext, IRequestHandler<PersonEdits.Create.Command, bool> createPersonEditsHandler)
        : base(applicationDbContext)
    {
        this._createPersonEditsHandler = createPersonEditsHandler;
    }

    public async Task<PersonData> Handle(Command request, CancellationToken cancellationToken)
    {
        using (var transaction = await this.ApplicationDbContext.Database.BeginTransactionAsync(cancellationToken))
        {
            await _createPersonEditsHandler.Handle(new PersonEdits.Create.Command(request.Person.Id), cancellationToken);

            var result = await Update(request, cancellationToken);
            transaction.Commit();
            return result;
        }
    }

    private async Task<PersonData> Update(Command request, CancellationToken cancellationToken)
    {
        var entity = request.Person.ToEntity();
        this.ApplicationDbContext.Persons.Update(entity);
        await this.ApplicationDbContext.SaveChangesAsync(cancellationToken);
        return PersonData.FromEntity(entity);
    }
}

ну а в PersonEdits чисто сохранение текущих полей в другую таблицу.


Ответы (1 шт):

Автор решения: A K

С транзакциями вопрос логично решать через специализированный pipeline:

public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var transactionOptions = new TransactionOptions
        {
            IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
        };
        using (var transaction = new TransactionScope(TransactionScopeOption.Required, transactionOptions,
        TransactionScopeAsyncFlowOption.Enabled))
        {
            // handle request handler
            var response = await next();
            // complete database transaction
            transaction.Complete();
            return response;
        }
    }
}

где делегат next как раз будет состоять из отдельных PreHandler, Handler и PostHandler.

Плюс ещё вот это видео на ютубе помогает понять декомпозицию Pipeline

введите сюда описание изображения

И хорошая статья на хабре, хотя код пришлось воспроизводить самому -- репозиториев к проекту увы нету, списывал код со слайдов презентации и пришлось догадываться о том, как организован проект и весь бойлерплейт код остался за рамками презентации.

→ Ссылка