如何检查DbContext是否有事务?

Sze*_*zer 9 c# entity-framework simple-injector

背景:我使用SimpleInjector作为IoC的WCF服务,它为每个WCF请求创建DbContext实例.

后端本身就是CQRS.CommandHandlers有很多装饰器(验证,授权,日志记录,不同处理程序组的一些通用规则等),其中一个是Transaction Decorator:

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当任何命令尝试链接在同一WCF请求中执行另一个命令时,会出现问题.我在这一行得到了一个预期的例外:

using (var transaction = _context.Database.BeginTransaction())
Run Code Online (Sandbox Code Playgroud)

因为我的DbContext实例已经有了一个事务.

有没有办法检查当前的交易存在?

Ale*_*rck 17

我想你正在寻找CurrentTransactionDbContext 的属性:

var transaction = db.Database.CurrentTransaction;
Run Code Online (Sandbox Code Playgroud)

然后你可以做这样的检查:

using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
   ...
}
Run Code Online (Sandbox Code Playgroud)

但是,如果并发方法正在使用该事务,我不知道如何知道何时提交事务.


Ric*_*Net 6

您可以或者可以使用TransactionScope类来创建环境事务范围,并管理覆盖范围内对(SQL)数据库所做的所有连接的事务,而不是使用来自实体框架的DbContext的事务.

SqlCommand如果你使用精确的(区分大小写的)连接字符串,它甚至会直接在同一个事务中SqlCommand.写入MessageQueue的消息也封装在同一事务中

它甚至可以同时管理与不同数据库的连接.它使用DTC窗口服务.请注意,如果需要,这是一个很难配置.通常,使用单个DB连接(或与同一个DB的多个连接),您将不需要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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是:正如qujck在评论中已经提到的那样,你错过了ICommandHandler作为原子操作的概念.一个命令处理程序不应该引用另一个命令处理程序.这不仅对交易有害,还会考虑这个:

想象一下应用程序的增长,你会将一些命令处理程序重构为后台线程,后台线程将在某些Windows服务中运行.在这个窗口服务中,没有PerWcfOperation生活方式.你现在需要一种LifeTimeScope生活方式给你的指挥官.因为你的设计允许它,顺便说一句!,你可以典型地将你的命令工具包装在LifetimeScopeCommandHandler装饰器中以启动它LifetimeScope.在您当前的设计中,单个命令处理程序引用其他命令处理程序,您将遇到问题,因为每个命令处理程序将在其自己的作用域中创建,因此获得注入的其他DbContext比其他命令处理程序!

因此,您需要进行一些重新设计并使命令操作员进行整体抽象,并为执行DbContext操作创建一个较低级别的抽象.