Как проверить, имеет ли 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())
{
   ...
}
однако я не уверен, как вы можете знать, когда совершать транзакцию, если она используется параллельными методами.
