TransactionScope和功能范围 - 这些连接是否在范围内?

时间:2011-11-14 20:00:45

标签: sql-server ado.net transactions transactionscope

假设您按照Microsoft example here设置了一个TransactionScope对象。现在假设您需要更新许多数据库表,并且您希望它们都在TransactionScope对象的范围内。不断地嵌套SqlConnection和SqlCommand对象10深会产生源代码混乱。 如果您调用其他创建连接的函数(例如,在您的数据访问层中),它们是否在TransactionScope对象的范围内?

示例:

' Assume variable "x" is a business object declared and populated with data.
Using scope As New TransactionScope()

    Dal.Foo.SaveProducts(x.Products)
    Dal.Foo.SaveCustomer(x.Customer)
    Dal.Foo.SaveDetails(x.Details) 
    ' more DAL calls ...
    Dal.Foo.SaveSomethingElse(x.SomethingElse)

    scope.Complete()

End Using

假设每个DAL函数都包含自己的using连接语句。例如:

Public Shared Sub SaveProducts(x As Object)

    Using conn As New SqlConnection("connection string")

        Using cmd As New SqlCommand("stored procedure name", conn)

            With cmd

                ' etc.                        

            End With

        End Using

    End Using

End Sub

2 个答案:

答案 0 :(得分:4)

是的,它们将在TransactionScope中。 TransactionScope基本上做的是创建一个Transaction对象并将Transaction.Current设置为该对象。

换句话说,这个:

Using scope As New TransactionScope()
    ... blah blah blah ...
End Using

与此基本相同:

try
{
    // Transaction.Current is a thread-static field
    Transaction.Current = new CommittableTransaction();
    ... blah blah blah ...
}
finally
{
    Transaction.Current.Commit(); // or Rollback(), depending on whether the scope was completed
    Transaction.Current = null;
}

当打开SqlConnection时,它会检查Transaction.Current(在此线程上)是否为null,如果它不为null,则它会登记(除非在连接字符串中为enlist = false)。所以这意味着SqlConnection.Open()不知道或不关心是否在此方法中打开了TransactionScope,或者是否调用了此方法。

(注意,如果您希望子方法中的SqlConnection不在事务中,则可以使用TransactionScopeOption.Suppress创建内部TransactionScope)

答案 1 :(得分:2)

创建TransactionScope时,您在TransactionScope存在时打开的所有连接都会自动加入事务(它们是“自动登记”)。所以你不需要传递连接字符串。

当SQL Server看到不同的事务时(即使它们都包含在一个DTC事务中),您可能仍然希望它们之间不共享锁。如果你打开太多的联系并进行大量的阅读和写作,你就会陷入僵局。

为什么不将活动连接放在某个全局位置并使用它?

一些研究后的更多信息。请阅读:TransactionScope automatically escalating to MSDTC on some machines?

如果你正在使用SQL Server 2008(可能是2012,但不是任何其他数据库),一些魔术在幕后完成,如果你打开两个SQL连接一个接一个,它们将被整合到一个SQL事务中,并且您不会遇到任何锁定问题。

然而,如果您使用的是其他数据库,或者您可能同时打开两个连接,您将获得DTC事务,这意味着SQL Server将无法正确管理锁定,您可以遇到非常不愉快和意外的僵局。

虽然很容易确保您只在SQL Server 2008上运行,但确保您不会同时打开两个连接会有点困难。很容易忘记它并做这样的事情:

class MyPersistentObject
{
    public void Persist()
    {
         using(SQLConnection conn=...)
         {
             conn.Open()
             WriteOurStuff()
             foreach(var child in this.PersistedChildren)
                 child.Persist()
             WriteLogMessage()
         }
    }
}

如果孩子的Persist方法打开另一个连接,您的交易将升级为DTC交易,并且您正面临潜在的锁定问题。

所以我仍然建议在一个地方保持连接并通过DAL使用它。它不必是一个简单的全局静态变量,您可以使用ConnectionManager.Current属性创建一个简单的ConnectionManager类,该属性将保存当前连接。将ConnectionManager.Current设为[ThreadStatic],您解决了大部分潜在问题。这就是TransactionScope在幕后的工作方式。

相关问题