Как проверить, имеет ли 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())
{
   ...
}

однако я не уверен, как вы можете знать, когда совершать транзакцию, если она используется параллельными методами.