Как проверить, имеет ли DbContext транзакцию?
фон: У меня есть служба WCF с SimpleInjector как IoC, которая создает экземпляр DbContext для запроса WCF.
сам сервер является CQRS. У CommandHandlers есть много декораторов (проверка, авторизация, ведение журнала, некоторые общие правила для разных групп обработчиков и т. д.), и один из них-декоратор транзакций:
public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _handler;
private readonly IMyDbContext _context;
private readonly IPrincipal _principal;
public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> handler,
IMyDbContext context, IPrincipal principal)
{
_handler = handler;
_context = context;
_principal = principal;
}
void ICommandHandler<TCommand>.Handle(TCommand command)
{
using (var transaction = _context.Database.BeginTransaction())
{
try
{
var user = _context.User.Single(x => x.LoginName == _principal.Identity.Name);
_handler.Handle(command);
_context.SaveChangesWithinExplicitTransaction(user);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
}
проблема возникает, когда любая команда пытается связать выполнение другой команды в том же запросе WCF. Я получил ожидаемое исключение в эта строка:
using (var transaction = _context.Database.BeginTransaction())
потому что мой экземпляр DbContext уже имеет транзакцию.
есть ли способ проверить наличие текущей транзакции?
2 ответов
вместо использования транзакции из DbContext Entity Framework вы можете или, возможно, должны использовать TransactionScope класс, который создает область внешней транзакции и управляет транзакциями всех подключений, сделанных к базе данных (SQL) под обложками.
он даже поставил бы прямой SqlCommand
в той же транзакции, если вы используете точную (чувствительную к регистру) connectionstring для SqlCommand
. Сообщений прописан в объект messagequeue также инкапсулируются в ту же транзакцию
он даже может управлять подключениями к различным базам данных одновременно. Он использует DTC служба windows для этого. Помните, что это боль, чтобы настроить, если это необходимо. Обычно с одним подключением к БД (или несколькими подключениями к одной БД) вам не понадобится DTC.
на TransactionScopeCommandHandlerDecorator
реализация тривиальна:
public class TransactionScopeCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> decoratee;
public TransactionScopeCommandHandlerDecorator(ICommandHandler<TCommand> decoratee)
{
this.decoratee = decoratee;
}
public void Handle(TCommand command)
{
using (var scope = new TransactionScope())
{
this.decoratee.Handle(command);
scope.Complete();
}
}
}
но: как qujck уже упоминалось в комментариях, вам не хватает понятия ICommandHandler
как атомарная операция. Один commandhandler никогда не должен ссылаться на другой commandhandler. Это не только плохо для транзакций, но и рассмотреть это:
представьте себе приложение, растет, и вы бы реструктурировать некоторые свои commandhandlers в фоновый поток, который будет работать в некоторые службы Windows. В этой службе windows a PerWcfOperation
образ жизни недоступен. Вам понадобится LifeTimeScope
образ жизни для вас commandhandlers сейчас. Потому что ваш дизайн позволяет это, что, кстати, здорово!, вы бы типично обернуть ваши commandhandlers в LifetimeScopeCommandHandler
декоратор начать LifetimeScope
. В вашем текущем дизайне, где один commandhandler ссылается на другие commandhandlers, вы столкнетесь с проблемой, потому что каждый commandhandler будет создан в своей собственной области a, таким образом, получает другой DbContext, чем другой commandhandlers!
так что вам нужно сделать некоторые редизайн и сделать ваши commandhandlers целостными абстракциями и создайте абстракцию нижнего уровня для выполнения операций DbContext.
Я думаю, вы ищете CurrentTransaction
свойство DbContext:
var transaction = db.Database.CurrentTransaction;
тогда вы можете сделать такую проверку:
using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
...
}
однако я не уверен, как вы можете знать, когда совершать транзакцию, если она используется параллельными методами.