SqlConnection并避免升级到MSDTC

Jul*_*anR 10 c# msdtc transactionscope sql-server-2012 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

从我可以告诉,卤面(当它在队列中处理消息)创建一个新的TransactionScope,以便在故障处理消息的情况下,东西可以回滚.现在,到目前为止,这本身一直很好.我可以SqlConnection在没有任何问题的情况下在Rebus消息处理程序中打开一个新的内容(但是,在同一个Rebus 中使用我们的遗留实体框架查询手动SqlConnections TransactionScope不起作用,但我现在不认为这是一个问题).但昨天我问了以下问题:

在Rebus中串行处理某种消息类型

答案似乎是使用Rebus的传奇功能.我尝试实现它并配置它,以便Rebus saga持久化到新的SQL Server数据库(具有不同的连接字符串).据推测,使用该SQL Server持久性打开SqlConnection它自己的,因为每当我尝试创建一个SqlConnection现在,我得到以下异常:

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

在配置和性能开销方面,我非常非常希望避免使用MSDTC .我可能错了,但它似乎也没有必要.

我认为这里发生的事情是Rebus创建了一个环境TransactionScope,并且SqlConnection它创建了该范围的enlists.当我尝试创建自己的内容时,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();
}
Run Code Online (Sandbox Code Playgroud)

我只是不确定这是否是正确的方法以及可能存在的缺点.

有小费吗?

更新:澄清一下,并不是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;
  }
}
Run Code Online (Sandbox Code Playgroud)

TransactionScopeRebus创建了一个,以及在SqlConnectionRebus打开之后,它会被调用.打开我的时候 SqlConnection,它会尝试登记并崩溃

Mar*_*ell 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

int CommitChanges(DbConnection connection, DbTransaction transaction = null)
{ ... }
Run Code Online (Sandbox Code Playgroud)

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

conn.Execute(sql, args, transaction: transaction);
Run Code Online (Sandbox Code Playgroud)