【问题标题】:How to check whether DbContext has transaction?如何检查 DbContext 是否有事务?
【发布时间】:2016-01-12 16:28:55
【问题描述】:

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

后端本身就是 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 实例已经有一个事务。

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

【问题讨论】:

  • @qujck 看完这篇文章我不明白IQuery&lt;TResult&gt;IDataQuery&lt;TResult&gt; 有什么区别。两者都返回数据。为什么我们需要第二个接口呢?有例子吗?
  • 它们都返回数据,但这些不同的抽象仅在装饰代码时才重要。您需要一个拥有整个操作的抽象,一个可以用TransactionDecorator 之类的东西包装的抽象。您还有其他较低级别的抽象,可以用与原子操作无关的横切关注点(例如 AuthoriseDecoratorLoggingDecorator)进行修饰。
  • @qujck 终于明白了。很好的解决方案,我一定会这样做。谢谢!

标签: c# entity-framework simple-injector


【解决方案1】:

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

var transaction = db.Database.CurrentTransaction;

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

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

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

【讨论】:

  • 我非常赞成您的回答,因为出于某种原因,我有 6.0.0 版本的 EF,它没有此属性。在 Update-Package EF 转到 6.1.3 并且属性在那里之后。非常感谢!
  • @Szer 是的,它已经很久没有在 EF 中了 :)
  • 我意识到你的回答比我的问题更直接的“回答”,但我应该承认,真正的答案是“糟糕的设计”,这就是@Ric.Net 所说的。
【解决方案2】:

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

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

它甚至可以同时管理与不同数据库的连接。它为此使用DTC 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();
        }
    }
}

但是:正如 qujck 在 cmets 中已经提到的,您缺少 ICommandHandler 作为原子操作的概念。一个 commandhandler 永远不应该引用另一个 commandhandler。这不仅不利于交易,还要考虑到这一点:

想象一下,当应用程序增长时,您会将一些命令处理程序重构为一个后台线程,该线程将在某些 Windows 服务中运行。在此 Windows 服务中,PerWcfOperation 生活方式不可用。您现在需要为您的指挥官提供LifeTimeScope 的生活方式。因为您的设计允许这样做,顺便说一句,这很棒!您通常会将您的命令处理程序包装在LifetimeScopeCommandHandler decorator 中以启动LifetimeScope。在您当前的设计中,当单个命令处理程序引用其他命令处理程序时,您会遇到问题,因为每个命令处理程序都将在其自己的范围内创建,因此注入的 DbContext 比其他命令处理程序要多!

因此您需要重新设计并让您的命令处理程序holistic abstractions 并创建一个较低级别的抽象来执行 DbContext 操作。

【讨论】:

  • 这是正确的方法,+1。 It even would put a direct SqlCommand in the same transaction if you would use the exact (case-sensitive) connectionstring for the SqlCommand 这不能保证。小心,这往往会在负载下失败。
  • @usr,你确定吗?我猜你是。你能指出一些关于这个的文件吗?在某些情况下,我们严重依赖此...非常感谢您的说明!
  • @Ric.Net 非常感谢!我会考虑重新设计链命令。但我应该提到这并不容易。在我的情况下,当客户端发送命令 AddEntityA 时,服务器应该将实体 A 添加到数据库,然后它还应该添加实体 B。 AddEntityB 命令有很多规则和检查以及自己的装饰器,所以如果实体 B 不能创建实体 A 也不会添加到数据库。这就是为什么我在命令中执行命令
  • @Ric.Net 如果您在同一个 TS 中打开多个连接,您通常会很幸运,因为连接池可能采用与 SQL Server 相同的物理连接。然后,您不会升级到 MSDTC。但是在负载下,池不一定会强制并且可能会返回新的连接或不同的连接。然后你随机升级(当应用程序必须工作最重要时在负载下)。这是魔鬼。如此讨厌的设计。
  • @usr 设计确实很糟糕! brrrrrr ...非常感谢您提供此信息。我会记住这一点!
猜你喜欢
  • 2018-05-09
  • 2017-07-11
  • 1970-01-01
  • 2011-02-05
  • 2022-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多