Две команды 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 шт):
С транзакциями вопрос логично решать через специализированный 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
И хорошая статья на хабре, хотя код пришлось воспроизводить самому -- репозиториев к проекту увы нету, списывал код со слайдов презентации и пришлось догадываться о том, как организован проект и весь бойлерплейт код остался за рамками презентации.
