如何检查DbContext是否有事务

本文关键字:DbContext 是否 事务 检查 何检查 | 更新日期: 2023-09-27 18:29:55

背景:我有一个使用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;
            }
        }
    }
}

当任何命令试图在同一WCF请求中链式执行另一个命令时,都会出现问题。我在这一行得到了一个预期的异常:

using (var transaction = _context.Database.BeginTransaction())

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

有什么方法可以检查当前交易的存在吗?

如何检查DbContext是否有事务

我认为您正在寻找DbContext:的CurrentTransaction属性

var transaction = db.Database.CurrentTransaction;

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

using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
   ...
}

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

您可以或可能应该使用TransactionScope类,而不是使用实体框架的DbContext中的事务,该类创建一个环境事务范围,并管理与(SQL)数据库的所有连接的事务。

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

它甚至可以同时管理到不同数据库的连接。为此,它使用DTC窗口服务。请注意,如果需要的话,这是一个很难配置的问题。通常,使用单个数据库连接(或到同一个数据库的多个连接),您不需要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作为原子操作的概念。一个命令处理程序永远不应该引用另一个命令处理器。这不仅对交易不利,而且考虑到这一点:

想象一下,应用程序正在增长,您将把一些命令处理程序重构为一个后台线程,该线程将在一些windows服务中运行。在此windows服务中,PerWcfOperation生活方式不可用。您现在需要一个LifeTimeScope的生活方式来满足您的命令处理程序。因为你的设计允许它,顺便说一句,这太棒了!,您通常会将命令处理程序封装在LifetimeScopeCommandHandler装饰器中以启动LifetimeScope。在您当前的设计中,如果单个命令处理程序引用其他命令处理程序,您将遇到问题,因为每个命令处理程序都将在自己的作用域中创建,因此会注入其他命令处理器之外的其他DbContext!

因此,您需要进行一些重新设计,使命令处理程序具有整体抽象,并为执行DbContext操作创建一个较低级别的抽象。