C# 如何检查DbContext是否有事务?

C# 如何检查DbContext是否有事务?,c#,entity-framework,simple-injector,C#,Entity Framework,Simple Injector,背景: 我有一个WCF服务,SimpleInjector作为IoC,它根据WCF请求创建DbContext实例 后端本身就是CQRS。CommandHandler有很多装饰器(验证、授权、日志记录、不同处理程序组的一些通用规则等),其中一个是事务装饰器: public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : IC

背景: 我有一个WCF服务,SimpleInjector作为IoC,它根据WCF请求创建DbContext实例

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

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;
            }
        }
    }
}
因为我的DbContext实例已经有一个事务


是否有任何方法可以检查当前事务是否存在?

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

var transaction = db.Database.CurrentTransaction;
然后,您可以进行如下检查:

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

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

您可以或可能应该使用创建环境事务作用域并管理与(SQL)的所有连接的事务的类,而不是使用实体框架的DbContext中的事务数据库被隐藏起来

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

它甚至可以同时管理到不同数据库的连接。它为此使用windows服务。请注意,如果需要配置,这将是一个难题。通常,对于单个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();
        }
    }
}
公共类TransactionScopeCommandHandlerDecorator
:ICommandHandler
{
私有只读ICommandHandler装饰对象;
公共事务处理范围CommandHandlerDecorator(ICommandHandler decoratee)
{
this.decoree=decoree;
}
公共无效句柄(TCommand命令)
{
使用(var scope=new TransactionScope())
{
this.decoree.Handle(命令);
scope.Complete();
}
}
}
但是:正如qujck在评论中已经提到的,您缺少作为原子操作的
ICommandHandler
的概念。一个commandhandler不应引用另一个commandhandler。这不仅对交易不利,而且考虑到这一点:

假设应用程序在增长,您将把一些命令处理程序重构为后台线程,该线程将在某些windows服务中运行。在此windows服务中,生活方式不可用。你现在需要一种生活方式。因为你的设计允许,顺便说一句,这太棒了!,您通常会将CommandHandler包装在一个文件中,以启动
生命周期范围。在您当前的设计中,如果单个commandhandler引用其他commandhandler,您将遇到问题,因为每个commandhandler都将在其自己的作用域中创建,因此会注入其他commandhandler以外的DbContext


因此,您需要进行一些重新设计,制作CommandHandler,并为执行DbContext操作创建一个较低级别的抽象。

阅读本文后,您缺少了一个@qujck。我不明白
IQuery
IDataQuery
之间的区别。两者都返回数据。为什么我们需要第二个接口?有一些例子吗?它们都返回数据,但这些不同的抽象只有在修饰代码时才重要。您需要一个拥有整个操作的抽象,一个可以用
TransactionDecorator
之类的东西包装的抽象。您还可以使用横切关注点来修饰其他较低级别的抽象,例如
authorizedCorator
LoggingDecorator
,这些抽象与原子操作无关。@qujck终于得到了它。很好的解决方案,我一定会做的。谢谢这是正确的方法,+1
如果您要为SqlCommand使用精确的(区分大小写的)connectionstring,它甚至会在同一事务中放置一个直接的SqlCommand
,这是不能保证的。小心,这会在负载下失败。@usr,你确定吗?我想你是的。你能指出一些关于这方面的文件吗?在某些情况下,我们严重依赖于此。。。非常感谢你的留言@Ric.Net非常感谢!我会考虑重新设计链命令。但我要说的是,这并不容易。在我的例子中,当客户端发送命令AddEntityA时,服务器应该将实体A添加到数据库中,然后还应该添加实体B。AddEntityB命令有很多规则和检查,并且有自己的装饰器,所以如果无法创建实体B,则实体A也不会添加到数据库中。这就是我在内部执行命令的原因command@Ric.Net如果在同一个TS中打开多个连接,您通常会很幸运,因为conn池可能与SQL Server具有相同的物理连接。然后,您就不会升级到MSDTC。但在负载情况下,池不一定能满足要求,可能会返回一个新的连接或另一个连接。然后随机升级(当应用程序必须最重要地工作时处于加载状态)。这是魔鬼。真是讨厌的设计。@usr讨厌的设计!brrrrrr。。。非常感谢您提供此信息。我会记住的!我非常支持你的答案,因为出于某种原因,我有6.0.0版本的EF,它没有这个属性。更新包后,EF转到6.1.3,属性在那里。非常感谢@Szer是的,它在EF中出现的时间不长:)我意识到你的答案对我的问题来说更直接,但我应该承认真正的答案是@Ric.Net所说的“糟糕的设计”。
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();
        }
    }
}