10

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

  • 对于查询,我们有一个静态工厂类,它的方法CreateOpenConnection无非就是new SqlConnection(myConnectionString)调用Open()它。在我们进行查询之前调用此方法,并在查询返回后释放连接。
  • 对于插入/更新/删除,我们使用工作单元模式,其中更改被批量化并通过如下调用提交到数据库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,以便在处理消息失败的情况下,可以回滚内容。现在,这本身到目前为止运行良好。我可以SqlConnection在 Rebus 消息处理程序中打开一个新的,没有任何问题(但是,在同一个 Rebus 中使用我们的旧实体框架查询手动 SqlConnectionsTransactionScope不起作用,但我现在不认为这是一个问题)。但是昨天我问了以下问题:

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()给我带来问题的原因,我很确定它会起作用,但我从来没有到达那里,因为我的查询失败了。

失败的一个例子:

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;
  }
}

当 aTransactionScope被 Rebus 创建时,以及 aSqlConnection被 Rebus 打开后,它会被调用。打开my SqlConnection后,它会尝试注册并崩溃

4

2 回答 2

3

我对您看到这一点感到有些惊讶,因为这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;
    }
}

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

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

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

conn.Execute(sql, args, transaction: transaction);
于 2013-05-31T10:13:08.897 回答
1

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

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

这可能是您看到的问题吗

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

于 2013-05-31T10:11:22.080 回答