【问题标题】:SqlConnection and avoiding promotion to MSDTCSqlConnection 和避免升级到 MSDTC
【发布时间】:2013-05-31 09:50:02
【问题描述】:

当我们需要在应用程序中进行数据库访问时,我们使用以下模式:

  • 对于查询,我们有一个静态工厂类,其方法为CreateOpenConnection,它只做new SqlConnection(myConnectionString) 并在其上调用Open()。在我们进行查询之前调用此方法,并在查询返回后释放连接。
  • 对于插入/更新/删除,我们使用工作单元模式,其中更改被批量处理并通过调用 work.Commit() 提交到数据库,如下所示:

work.Commit:

using (var tranScope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
    using (var conn = DapperFactory.CreateOpenConnection())
    {
      var count = _changeTracker.CommitChanges(conn);

      tranScope.Complete();

      return count;
    }
}

这似乎非常适合作为 Web 服务的一部分进行一般使用,但是当我尝试将它与 Rebus 结合使用时,这给我带来了 MSDTC 麻烦。

据我所知,Rebus(当它处理队列中的消息时)会创建一个新的TransactionScope,以便在处理消息失败的情况下,可以回滚内容。现在,这本身到目前为止运行良好。我可以在 Rebus 消息处理程序中打开一个新的 SqlConnection 而不会出现任何问题(但是,在同一 Rebus TransactionScope 中使用我们的旧实体框架查询手动 SqlConnections 不起作用,但我不'现在不认为这是一个问题)。但是昨天我问了以下问题:

Serial processing of a certain message type in Rebus

答案似乎是使用 Rebus 的 saga 功能。我尝试实现它并对其进行配置,以便将 Rebus 传奇持久化到一个新的 SQL Server 数据库(具有不同的连接字符串)。据推测,使用该 SQL Server 持久性会打开它自己的 SqlConnection,因为任何时候我现在尝试创建 SqlConnection,都会收到以下异常:

分布式事务管理器 (MSDTC) 的网络访问已被禁用。请使用组件服务管理工具在 MSDTC 的安全配置中启用 DTC 以进行网络访问。

启用 MSDTC 是我非常、非常希望避免做的事情,考虑到配置和性能开销。我可能错了,但这似乎也没有必要。

我认为这里发生的是 Rebus 创建了一个环境 TransactionScope 并且它创建的 SqlConnection 加入了该范围。当我尝试创建自己的SqlConnection 时,它也尝试加入该环境范围,并且由于涉及多个连接,它被提升为失败的 MSDTC。

我对如何解决此问题有一个想法,但我不知道这样做是否正确。我会做的是:

  • Enlist=false 添加到我的应用程序的连接字符串中,这样它就不会加入环境事务。
  • 修改Commit 方法,使其不会创建新的TransactionScope(我的连接不会再订阅它,因为我刚刚告诉过它不应该订阅它),而是使用conn.BeginTransaction

像这样:

var transaction = conn.BeginTransaction();

try
{
  var count = _changeTracker.CommitChanges(conn);
  transaction.Commit();
  return count;
}
catch
{
  transaction.Rollback();
  throw;
}
finally
{
  transaction.Dispose();
}

我只是不确定这是否是正确的方法以及可能的缺点是什么。

有什么建议吗?

更新:澄清一下,给我带来问题的不是work.Commit(),我很确定它会起作用,但我从来没有到达那里,因为我的查询 em> 失败了。

失败的例子:

public int? GetWarehouseID(int appID)
{
  var query = @"
select top 1 ID from OrganizationUnits o
where TypeID & 16 = 16 /* warehouse */";

  using (var conn = _dapper.OpenConnection())
  {
    var id = conn.Query<int?>(query).FirstOrDefault();

    return id;
  }
}

当 Rebus 创建了 TransactionScope 时,以及 Rebus 打开了 SqlConnection 之后,都会调用它。在打开 my SqlConnection 时,它会尝试注册并崩溃

【问题讨论】:

  • 如果您使用“Enlist=false”,那肯定会让您的TransactionScope 毫无意义?因为连接不会在其中
  • 同样,您的BeginTransaction 代码也不使用事务 - 需要在命令中明确指定 ADO.NET 事务,因此您需要将transaction 传递给CommitChanges,确定吗?
  • 您能否明确说明您使用的是哪个版本的sql server?

标签: c# sql-server-2012 transactionscope msdtc rebus


【解决方案1】:

你看到这个我有点惊讶,因为RequiresNew 应该意味着它与其他事务是隔离的;通常,此消息意味着已在事务范围内激活了 2 个连接 - 您确定在该块内没有其他代码创建/打开连接吗?

您提出的解决方案应该可行 - 尽管在某些方面TransactionScopeOption.Suppress 可能比更改配置更方便(但两者都应该可行)。但是,有一个问题:ADO.NET 事务必须传递给各个命令,因此您需要(也稍微​​整理一下代码):

using(var transaction = conn.BeginTransaction()) {
    try {
        var count = _changeTracker.CommitChanges(conn, transaction);
        transaction.Commit();
        return count;
    } catch {
        transaction.Rollback();
        throw;
    }
}

CommitChanges 接受交易 - 可能使用可选参数:

int CommitChanges(DbConnection connection, DbTransaction transaction = null)
{ ... }

您对DapperFactory 的命名表明您正在使用“dapper” - 在这种情况下,您可以将其传递给“dapper”,无论它是否为空,即

conn.Execute(sql, args, transaction: transaction);

【讨论】:

  • 抱歉,澄清(并查看我的更新)不是 Commit 失败,而是查询。我的提交代码只是为了展示我建议如何修改它,如果我添加Enlist=false 并仍然保持Commit 是原子的(因为它确实不再对我创建的TransactionScope 做任何事情。并且传递交易的部分是我忽略的,谢谢提醒:)
  • @JulianR 我不认为Commit 错误;我试图说明 2 点 - 首先,代码需要通过交易 - 然后我忘记包含在示例中(d'oh!请参阅编辑); 其次你的try/catch/finally过于复杂,可以简化
  • 是的,我指的是您希望RequiresNew 起作用的评论,它可能确实如此。但是假设我对传入DbConnection 进行了必要的更改,你认为我的新方法会奏效吗?我担心的是插入/更新/删除不是原子的,或者enlist=false 对查询有一些负面/奇怪的后果。在某种程度上,如果你明白我的意思的话,感觉就像通过吞咽来“修复”一个异常:)
  • @JulianR 切换到一个非常不同的事务范式很难“吞下它”;但是,是的,我同意不理解为什么您的原始方法不起作用是不令人满意的。这取决于您想花多长时间尝试修复它,而不是转向有用的东西
【解决方案2】:

这在很大程度上取决于您使用的 SQL Server 版本。有关解决类似问题的另一个 SO 问题,请参阅 here

这与 SQL 2005 和 SQL 2008 在处理同一 TransactionScope 内的多个连接方面的不同之处有关。即 SQL 2008 可以在同一个 TransactionScope 中打开多个连接,而无需升级到 MSDTC。

这可能是您看到的问题

如果是这种情况,我认为只有两个选项是升级到 SQL 2008 或启用 MSDTC。我知道这两种选择都可能令人头疼。

【讨论】:

  • 需要强调的是,2008的处理方式仍然只适用于两个连接的connection-string和thread-identity相同的情况下,并且连接是串联打开的(不是同时打开的)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
相关资源
最近更新 更多