在我们的项目中,我们使用 TransactionScope 来确保我们的数据访问层在事务中执行它的操作。我们的目标是不需要在最终用户的机器上启用 MSDTC 服务。
问题是,在我们一半的开发人员机器上,我们可以在禁用 MSDTC 的情况下运行。另一半必须启用它,否则他们会收到“[SERVER] 上的 MSDTC 不可用”错误消息。
这真的让我摸不着头脑,并让我认真考虑回滚到基于 ADO.NET 事务对象的类似 TransactionScope 的自制解决方案。这看起来很疯狂 - 在我们一半开发人员的工作(并且不会升级)的相同代码确实会升级到其他开发人员的代码。
我希望有一个更好的答案来跟踪为什么交易会升级到 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
- 开发 2:Windows 7 x86 SQL2008
- 开发 3:Windows 7 x64
SQL2005SQL2008
它不适用于的开发人员:
- 开发 4:Windows 7 x64,
SQL2008SQL2005 - 开发 5:Windows Vista x86、SQL2005
- 开发 6:Windows XP X86、SQL2005
- 我的家用电脑:Windows Vista Home Premium、x86、SQL2005
我应该补充一点,为了解决问题,所有机器都已使用 Microsoft Update 提供的所有内容进行了全面修补。
更新1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/描述了一个类似的问题......早在2006年!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - 阅读该代码示例,它清楚地演示了嵌套秒连接(到第二个 SQL 服务器,实际上)这将升级为 DTC。 我们没有在我们的代码中这样做——我们没有使用不同的 SQL 服务器,也没有使用不同的连接字符串,也没有打开嵌套的辅助连接——不应该升级到 DTC。
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx(从 2005 年开始)谈到了在连接到 SQL2000 时如何升级到 DTC。我们正在使用 SQL2005/2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN 关于事务升级。
该 MSDN 事务升级页面指出,以下情况将导致事务升级到 DTC:
- 在事务中登记了至少一个不支持单阶段通知的持久资源。
- 事务中至少包含两个支持单阶段通知的持久资源。例如,征用单个连接不会导致事务被提升。但是,每当您打开导致数据库登记的第二个数据库连接时,System.Transactions 基础结构会检测到它是事务中的第二个持久资源,并将其升级为 MSDTC 事务。
- 调用将事务“编组”到不同应用程序域或不同进程的请求。例如,跨应用程序域边界的事务对象的序列化。事务对象是按值编组的,这意味着任何跨应用程序域边界(即使在同一进程中)传递它的尝试都会导致事务对象的序列化。您可以通过调用将事务作为参数的远程方法来传递事务对象,也可以尝试访问远程事务服务组件。这会序列化事务对象并导致升级,就像跨应用程序域序列化事务时一样。它正在分发,本地事务管理器不再足够。
我们没有遇到#3。#2 没有发生,因为一次只有一个连接,而且它也连接到一个“持久资源”。#1 有没有可能发生?某些 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:如果这多个 SqlConnections 是嵌套的,即同时打开两个或多个 SqlConnections,TransactionScope 将立即升级为 DTC。
- 警告 #2:如果一个额外的 SqlConnection 被打开到不同的“持久资源”(即:不同的 SQL Server),它将立即升级到 DTC
SQL2005:
- 不允许单个 TransactionScope 期间内的多个连接。当/如果打开第二个 SqlConnection 时,它将升级。
更新 4
为了使这个问题更加有用,并且为了更清楚起见,以下是如何让 SQL2005 升级为 DTC 的方法 SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
这对我来说似乎很糟糕,但我想我可以理解是否每次调用SqlConnection.Open()
都是从连接池中获取的。
“不过,为什么会发生这种情况呢?” 好吧,如果您在打开该连接之前对它使用 SqlTableAdapter,则 SqlTableAdapter 将打开和关闭连接,从而有效地为您完成事务,因为您现在无法重新打开它。
因此,基本上,为了成功使用 SQL2005 的 TransactionScope,您需要有某种全局连接对象,该对象从第一个 TransactionScope 实例化的那一刻起保持打开状态,直到不再需要它为止。除了全局连接对象的代码异味之外,先打开连接后关闭它与尽可能晚打开连接并尽快关闭它的逻辑相矛盾。