在我们的项目中,我们使用TransactionScope来确保我们的数据访问层在事务中执行它的操作。我们的目标是不要求在最终用户的机器上启用MSDTC服务。

问题是,在一半的开发人员机器上,我们可以在禁用MSDTC的情况下运行。另一半必须启用它,否则他们会得到“MSDTC on [SERVER] is unavailable”错误消息。

这真的让我摸不着头脑,并让我认真考虑回到一个基于ADO的自制的类似transactionscope的解决方案。NET事务对象。这看起来很疯狂——同样的代码在我们的开发人员的一半上运行(并且没有升级),却在另一个开发人员的代码上升级。

我希望Trace能更好地回答为什么事务升级到DTC,但不幸的是它没有。

下面是一个会导致问题的示例代码,在试图升级的机器上,它试图在第二个连接上升级。open()(是的,当时没有其他连接打开。)

using (TransactionScope transactionScope = new TransactionScope() {
   using (SqlConnection connection = new SqlConnection(_ConStr)) {
      using (SqlCommand command = connection.CreateCommand()) {
         // prep the command
         connection.Open();
         using (SqlDataReader reader = command.ExecuteReader()) {
            // use the reader
            connection.Close();
         }
      }
   }

   // Do other stuff here that may or may not involve enlisting 
   // in the ambient transaction

   using (SqlConnection connection = new SqlConnection(_ConStr)) {
      using (SqlCommand command = connection.CreateCommand()) {
         // prep the command
         connection.Open();  // Throws "MSDTC on [SERVER] is unavailable" on some...

         // gets here on only half of the developer machines.
      }
      connection.Close();
   }

   transactionScope.Complete();
}

我们已经深入研究并试图解决这个问题。这里有一些关于它工作的机器的信息:

开发1:Windows 7 x64 SQL2008 Dev 2: Windows 7 x86 SQL2008 Dev 3: Windows 7 x64 SQL2005 SQL2008

不适用的开发人员:

Dev 4: Windows 7 x64, SQL2008 SQL2005 开发5:Windows Vista x86, SQL2005 Dev 6: Windows XP X86, SQL2005 我的家庭电脑:Windows Vista家庭高级版,x86, SQL2005

我应该补充一点,为了找到问题,所有的机器都已经用微软更新提供的所有东西打了完整的补丁。

更新1:

http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ describes a similar problem...back in 2006! http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - read that code sample, it clearly demonstrates a nested-second connection (to a second SQL server, actually) which will escalate to DTC. We are not doing this in our code - we're not using different SQL servers, nor different connection strings, nor do we have nested secondary connections opening - there should not be escalation to DTC. http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (from 2005) talks about how escalation to DTC will always happen when connecting to SQL2000. We're using SQL2005/2008 http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN on transaction escalation.

该MSDN事务升级页面指出,以下条件将导致事务升级到DTC:

At least one durable resource that does not support single-phase notifications is enlisted in the transaction. At least two durable resources that support single-phase notifications are enlisted in the transaction. For example, enlisting a single connection with does not cause a transaction to be promoted. However, whenever you open a second connection to a database causing the database to enlist, the System.Transactions infrastructure detects that it is the second durable resource in the transaction, and escalates it to an MSDTC transaction. A request to "marshal" the transaction to a different application domain or different process is invoked. For example, the serialization of the transaction object across an application domain boundary. The transaction object is marshaled-by-value, meaning that any attempt to pass it across an application domain boundary (even in the same process) results in serialization of the transaction object. You can pass the transaction objects by making a call on a remote method that takes a Transaction as a parameter or you can try to access a remote transactional-serviced component. This serializes the transaction object and results in an escalation, as when a transaction is serialized across an application domain. It is being distributed and the local transaction manager is no longer adequate.

我们没有经历第三种情况。#2没有发生,因为一次只有一个连接,而且它也是连接到单个“持久资源”。有可能发生第一条吗?某些SQL2005/8配置导致它不支持单阶段通知?

更新2:

重新调查,我个人,每个人的SQL Server版本——“Dev 3”实际上有SQL2008,而“Dev 4”实际上是SQL2005。这教训了我再也不要相信我的同事了。,)由于数据的变化,我很确定我们找到了问题所在。我们的SQL2008开发人员没有遇到这个问题,因为SQL2008包含了大量SQL2005所没有的令人敬畏的功能。

它还告诉我,因为我们将支持SQL2005,我们不能像以前那样使用TransactionScope,如果我们想使用TransactionScope,我们将需要传递一个SqlConnection对象…在SqlConnection不能轻易传递的情况下,这似乎是有问题的…它只是闻起来像global-SqlConnection实例。皮尤!

更新3

在这个问题中澄清一下

SQL2008:

允许在单个TransactionScope中有多个连接(如上面的示例代码所示)。 警告#1:如果那些多个sqlconnection是嵌套的,也就是说,两个或多个sqlconnection同时被打开,TransactionScope将立即升级为DTC。 警告#2:如果一个额外的SqlConnection被打开到一个不同的“持久资源”(即:一个不同的SQL Server),它将立即升级为DTC

SQL2005:

不允许在一个TransactionScope内有多个连接。当/如果打开第二个SqlConnection时,它将升级。

更新4

为了让这个问题变得更有用,也为了更清晰,这里介绍了如何通过一个SqlConnection将SQL2005升级为DTC:

using (TransactionScope transactionScope = new TransactionScope()) {
   using (SqlConnection connection = new SqlConnection(connectionString)) {
      connection.Open();
      connection.Close();
      connection.Open(); // escalates to DTC
   }
}

这似乎打破了我,但我想我可以理解,如果每个调用SqlConnection.Open()是从连接池抓取。

“但为什么会发生这种情况呢?”如果您在连接打开之前使用SqlTableAdapter, SqlTableAdapter将打开和关闭连接,有效地为您完成事务处理,因为您现在不能重新打开它。

因此,基本上,为了成功地将TransactionScope与SQL2005一起使用,您需要某种全局连接对象,该对象从实例化第一个TransactionScope开始一直保持打开状态,直到不再需要为止。除了全局连接对象的代码气味之外,首先打开连接并最后关闭它与尽可能晚打开连接和尽快关闭连接的逻辑是不一致的。


当前回答

我不太确定嵌套连接是否是问题所在。我正在调用SQL server的本地实例,它不生成DTC??

    public void DoWork2()
    {
        using (TransactionScope ts2 = new TransactionScope())
        {
            using (SqlConnection conn1 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;"))
            {
                SqlCommand cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                cmd.Connection = conn1;
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();

                using (SqlConnection conn2 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;Connection Timeout=100"))
                {
                    cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                    cmd.Connection = conn2;
                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
            }

            ts2.Complete();
        }
    }

其他回答

确保你的connectionString没有将池化设置为false。这将为TransactionScope中的每个新的SqlConnection创建一个新连接,并将其升级为DTC。

我不知道为什么这个答案被删除了,但这似乎有一些相关的信息。

回答是8月4日17:42爱德华多

在连接字符串上设置Enlist=false以避免事务上的自动征募。 手动将连接征募为事务范围内的参与者。[原文过时] 或者这样做:如何防止自动MSDTC升级[archive.is]

如果在内部使用多个连接,TransactionScope总是升级为DTC事务。上面的代码在禁用DTC的情况下工作的唯一方法是,如果您两次从连接池中获得相同的连接,那么可能性很大。

“问题是,在我们一半的开发人员机器上,我们可以在禁用MSDTC的情况下运行。” 你确定它被禁用了吗?

当连接到2005时,该代码将导致升级。

请查看MSDN - http://msdn.microsoft.com/en-us/library/ms172070.aspx上的文档

Promotable Transactions in SQL Server 2008 In version 2.0 of the .NET Framework and SQL Server 2005, opening a second connection inside a TransactionScope would automatically promote the transaction to a full distributed transaction, even if both connections were using identical connection strings. In this case, a distributed transaction adds unnecessary overhead that decreases performance. Starting with SQL Server 2008 and version 3.5 of the .NET Framework, local transactions are no longer promoted to distributed transactions if another connection is opened in the transaction after the previous transaction is closed. This requires no changes to your code if you are already using connection pooling and enlisting in transactions.

我无法解释为什么Dev 3: Windows 7 x64, SQL2005成功,而Dev 4: Windows 7 x64失败。你确定不是反过来吗?

我不太确定嵌套连接是否是问题所在。我正在调用SQL server的本地实例,它不生成DTC??

    public void DoWork2()
    {
        using (TransactionScope ts2 = new TransactionScope())
        {
            using (SqlConnection conn1 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;"))
            {
                SqlCommand cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                cmd.Connection = conn1;
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();

                using (SqlConnection conn2 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;Connection Timeout=100"))
                {
                    cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                    cmd.Connection = conn2;
                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
            }

            ts2.Complete();
        }
    }