SqlConnection并避免升级到MSDTC

时间:2013-05-31 09:50:02

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

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

  • 对于查询,我们有一个静态工厂类,其方法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而没有任何问题(但是,使用我们的遗留实体框架查询手动SqlConnections在同一个Rebus TransactionScope内部不起作用,但我现在不认为这是一个问题)。但昨天我问了以下问题:

Serial processing of a certain message type in Rebus

答案似乎是使用Rebus的传奇功能。我尝试实现它并配置它,以便Rebus saga持久化到新的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;
  }
}

当Rebus创建TransactionScope时,以及Rebus打开SqlConnection后,会调用此方法。打开我的 SqlConnection后,它会尝试登记并崩溃

2 个答案:

答案 0 :(得分: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;
    }
}

其中CommitChanges接受一个事务 - 可能使用可选参数:

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

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

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

答案 1 :(得分:1)

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

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

这可能是你看到的问题

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