確保交易的新利器(TransactionScope)初體驗-Part 1(注意Scope.Complete的位置)

從.NET Framework 2.0以後,有個新的東西稱之為TransactionScope,他可以讓我們在處理交易的時候,可以確保交易的完整性,並且使用十分的簡便,小喵在此測試一下他的撰寫方式與結果,在測試過程中,驚人的發現了MSDN的一個錯誤內容。

從.NET Framework 2.0以後,有個新的東西稱之為TransactionScope,他可以讓我們在處理交易的時候,可以確保交易的完整性,並且使用十分的簡便,小喵在此測試一下他的撰寫方式與結果,在測試過程中,驚人的發現了MSDN範例的一個錯誤內容。

小喵以北風資料庫當作小喵測試的資料。就選用Employee這個資料表來測試看看,我們先看一下資料表的Layout以及他本來資料的狀況

資料表Layout

ts001

原本資料狀況

ts002

小喵會寫兩段更改資料的語法,分別

  1. 將EmployeeID=1的LastName最後一個字加上【X】字元,這部分是應該可以過得
  2. 小喵試著要把EmployeeID=9的FirstName設定為【ABC123456789】,從資料表Layout看出,FirstName欄位大小是10個字元,因此這個應該會出現錯誤

理想的狀況應該是,他會Rollback,讓1.的變化也回覆

首先,要看一下TransactionScope的說明請參考以下鏈結

http://msdn.microsoft.com/zh-tw/library/system.transactions.transactionscope(VS.80).aspx

接著就是測試的程式碼:

小喵把北風加入asp.net的專案中,在畫面上安排一個Button1,並且從伺服器總館中把Empolyee這個資料表拉到設計中,系統會自動產生Employee這個資料表的GridView與SqlDataSouce以及在Web.Config中,會把ConnectionString準備好。接著小喵雙擊Button1開始寫Button1的程式碼

一開始,當然要Imports相關的NameSpace,在撰寫時,發現彈不出System.Transactions,於是小喵先把System.Transactions加入參考。然後在最上方加入Imports


Imports System
Imports System.Transactions
Imports System.Data
Imports System.Data.SqlClient

接著就撰寫Button1 Click事件的內容,小喵依照MSDN上面的範例,撰寫以下的內容


        Using Scope As New TransactionScope
            Try

                Dim ConnStr As String = ConfigurationManager.ConnectionStrings("NORTHWNDConnectionString1").ConnectionString
                Using Conn As New SqlConnection(ConnStr)
                    Conn.Open()
                    Dim SqlTxt As String = ""
                    Dim Cmmd As SqlCommand

                    SqlTxt += " Update Employees "
                    SqlTxt += " Set LastName = LastName + 'X' "
                    SqlTxt += " Where EmployeeID = @EmployeeID "
                    SqlTxt += "  "
                    Cmmd = New SqlCommand(SqlTxt, Conn)
                    Cmmd.Parameters.AddWithValue("@EmployeeID", 1)
                    Cmmd.ExecuteNonQuery()
                    '---以上這一段理論上是可以正常通過的


                    '---以下這段因為超過欄位大小,應該會有Exception
                    SqlTxt = ""
                    SqlTxt += " Update Employees "
                    SqlTxt += " Set FirstName = 'ABC1234567890' "
                    SqlTxt += " Where EmployeeID = @EmployeeID "
                    SqlTxt += "  "

                    Cmmd = New SqlCommand(SqlTxt, Conn)
                    Cmmd.Parameters.AddWithValue("@EmployeeID", 9)
                    Cmmd.ExecuteNonQuery()

                End Using

            Catch ex As Exception
                Response.Write(ex.Message.ToString)

            End Try

            ' The Complete method commits the transaction. If an exception has been thrown,
            ' Complete is called and the transaction is rolled back.

            '設定交易完成
            Scope.Complete()


        End Using
        Me.GridView1.DataBind()

結果太令人意外了,竟然沒有把這兩段維護包在一個Transaction中,小喵從以往的經驗裡面,判斷,Scope.Complete()應該要在沒有Exception的狀況,才進行。很顯然的,他的位置不對,因此小喵覺得他的範例中註解的那句話不正確。小喵試著把他的位置調整一下,調整後如下:


        Using Scope As New TransactionScope
            Try

                Dim ConnStr As String = ConfigurationManager.ConnectionStrings("NORTHWNDConnectionString1").ConnectionString
                Using Conn As New SqlConnection(ConnStr)
                    Conn.Open()
                    Dim SqlTxt As String = ""
                    Dim Cmmd As SqlCommand

                    SqlTxt += " Update Employees "
                    SqlTxt += " Set LastName = LastName + 'X' "
                    SqlTxt += " Where EmployeeID = @EmployeeID "
                    SqlTxt += "  "
                    Cmmd = New SqlCommand(SqlTxt, Conn)
                    Cmmd.Parameters.AddWithValue("@EmployeeID", 1)
                    Cmmd.ExecuteNonQuery()
                    '---以上這一段理論上是可以正常通過的


                    '---以下這段因為超過欄位大小,應該會有Exception
                    SqlTxt = ""
                    SqlTxt += " Update Employees "
                    SqlTxt += " Set FirstName = 'ABC1234567890' "
                    SqlTxt += " Where EmployeeID = @EmployeeID "
                    SqlTxt += "  "

                    Cmmd = New SqlCommand(SqlTxt, Conn)
                    Cmmd.Parameters.AddWithValue("@EmployeeID", 9)
                    Cmmd.ExecuteNonQuery()

                End Using

                '正確的位置應該在這裡,也就是當有意外狀況的時候,應該要不能Scope.Complete()
                '設定交易完成
                Scope.Complete()


            Catch ex As Exception
                Response.Write(ex.Message.ToString)

            End Try

        End Using
        Me.GridView1.DataBind()

經過這樣位置調整後,終於有了預期的效果,當第二個維護發生狀況時,同時把第一個維護的結果Rollback了。因此請大家要使用TransactionScope的時候,要特別注要安排Scope.Complete的位置要正確,才能達到預期的效果。

結論:

使用TransactionScope用法相當簡單,只要宣告Scope,並把要維護的過程包起來,就能夠確保維護過程的交易(Transaction)完整性。不過使用時要特別特別注意Scope.Complete放的位置,要在確定沒有Exception的狀況下,才使用Scope.Complete來讓Transaction進行Commit

另外,TransactionScope不只是這樣而已,使用物件導向設計,在一個商業邏輯中用了數個物件維護資料,也可透過TransactionScope來確保交易的完整性,請看下一篇文章【確保交易的新利器(TransactionScope)初體驗-Part 2(物件維護的交易確保)


以下是簽名:


Microsoft MVP
Visual Studio and Development Technologies
(2005~2019/6) 
topcat
Blog:http://www.dotblogs.com.tw/topcat