2010-04-23 170 views
15

在我的C#代碼中,我使用的是TransactionScope,因爲我被告知不要依賴我的sql程序員將始終使用事務,並且我們有責任和yada yada。TransactionScope和交易

話雖如此

它看起來像TransactionScope的對象滾,之前的SqlTransaction回來嗎?這是可能的,如果是的話,在事務中包裝TransactionScope的正確方法是什麼。

這裏是SQL測試

CREATE PROC ThrowError 
AS 

BEGIN TRANSACTION --SqlTransaction 
SELECT 1/0 

IF @@ERROR<> 0 
BEGIN 
    ROLLBACK TRANSACTION --SqlTransaction 
    RETURN -1 
END 
ELSE 
BEGIN 
    COMMIT TRANSACTION --SqlTransaction 
    RETURN 0 
END 

go 

DECLARE @RESULT INT 

EXEC @RESULT = ThrowError 

SELECT @RESULT 

如果我運行此我通過0得到公正的鴻溝,從C#代碼中,我得到一個額外的錯誤信息返回-1

呼叫

遇到零分錯誤。
EXECUTE後的事務計數表示缺少COMMIT或ROLLBACK TRANSACTION表達式。上一個計數= 1,當前計數= 0

如果我給了SQL事務的名稱,然後

不能回滾的SqlTransaction。 未找到該名稱的事務或保存點。 EXECUTE後的事務計數表示COMMIT或ROLLBACK TRANSACTION語句丟失。上一個計數= 1,當前計數= 2

有時似乎次數上升,直到應用程序完全退出

C#的只是

 using (TransactionScope scope = new TransactionScope()) 
     { 
      ... Execute Sql 

      scope.Commit() 
     } 

編輯:

SQL代碼必須適用於2000和2005年

回答

21

有一個大規模升級到SQL Server 2005中處理錯誤這些文章是相當廣泛:Error Handling in SQL 2005 and Later by Erland SommarskogError Handling in SQL 2000 – a Background by Erland Sommarskog

最好的辦法是這樣的:

創建存儲過程,如:

CREATE PROCEDURE YourProcedure 
AS 
BEGIN TRY 
    BEGIN TRANSACTION --SqlTransaction 
    DECLARE @ReturnValue int 
    SET @ReturnValue=NULL 

    IF (DAY(GETDATE())=1 --logical error 
    BEGIN 
     SET @ReturnValue=5 
     RAISERROR('Error, first day of the month!',16,1) --send control to the BEGIN CATCH block 
    END 

    SELECT 1/0 --actual hard error 

    COMMIT TRANSACTION --SqlTransaction 
    RETURN 0 

END TRY 
BEGIN CATCH 
    IF XACT_STATE()!=0 
    BEGIN 
     ROLLBACK TRANSACTION --only rollback if a transaction is in progress 
    END 

    --will echo back the complete original error message to the caller 
    --comment out if not needed 
    DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int 

    SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE() 
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine) 

    RETURN ISNULL(@ReturnValue,1) 

END CATCH 

GO 

但是這隻適用於SQL Server 2005以上。如果不使用SQL Server 2005中的TRY-CATCH塊,您將很難刪除SQL Server發回的所有消息。該extra messages你指的是通過回滾的是如何使用@@ TRANCOUNT處理的性質造成的:

http://www.sommarskog.se/error-handling-I.html#trancount

@@ TRANCOUNT是 反映嵌套 交易水平的全局變量。每個由1 BEGIN TRANSACTION 增加@@ TRANCOUNT,並且每個 COMMIT TRANSACTION減小 @@ TRANCOUNT由1沒有實際上是 致力於直到@@ TRANCOUNT達到0 回滾事務回滾 一切到最外面的BEGIN TRANSACTION(除非您已使用 頗具異國情調的SAVE TRANSACTION),並且 強制@@ trancount爲0,關注 以前的值。

當你退出的存儲過程,如果 @@ TRANCOUNT不具有相同的 價值,因爲它有當程序 動工執行,SQL Server將 錯誤266是不是引發此錯誤, 不過,如果該過程從觸發器直接調用 ,或直接調用 。也不是,如果你 與組隱 交易上運行

如果您不想獲得有關交易的警告數不匹配,你只需要在任何一次打開一個交易它提出。你可以通過創建你所有的程序來做到這一點:

CREATE PROC YourProcedure 
AS 
DECLARE @SelfTransaction char(1) 
SET @SelfTransaction='N' 

IF @@trancount=0 
BEGIN 
    SET @SelfTransaction='Y' 
    BEGIN TRANSACTION --SqlTransaction 
END 

SELECT 1/0 

IF @@ERROR<> 0 
BEGIN 
    IF @SelfTransaction='Y' 
    BEGIN 
     ROLLBACK TRANSACTION --SqlTransaction 
    END 
    RETURN -1 
END 
ELSE 
BEGIN 
    IF @SelfTransaction='Y' 
    BEGIN 
     COMMIT TRANSACTION --SqlTransaction 
    END 
    RETURN 0 
END 

GO 

通過這樣做,如果你還沒有在事務中,你只能發出事務命令。如果你用這種方式編寫所有的程序,只有發佈BEGIN TRANSACTION的程序或C#代碼實際上會發出COMMIT/ROLLBACK,並且事務計數總是匹配的(你不會得到錯誤)。

在C#

TransactionScope Class Documentation

static public int CreateTransactionScope(
    string connectString1, string connectString2, 
    string commandText1, string commandText2) 
{ 
    // Initialize the return value to zero and create a StringWriter to display results. 
    int returnValue = 0; 
    System.IO.StringWriter writer = new System.IO.StringWriter(); 

    try 
    { 
     // Create the TransactionScope to execute the commands, guaranteeing 
     // that both commands can commit or roll back as a single unit of work. 
     using (TransactionScope scope = new TransactionScope()) 
     { 
      using (SqlConnection connection1 = new SqlConnection(connectString1)) 
      { 
       // Opening the connection automatically enlists it in the 
       // TransactionScope as a lightweight transaction. 
       connection1.Open(); 

       // Create the SqlCommand object and execute the first command. 
       SqlCommand command1 = new SqlCommand(commandText1, connection1); 
       returnValue = command1.ExecuteNonQuery(); 
       writer.WriteLine("Rows to be affected by command1: {0}", returnValue); 

       // If you get here, this means that command1 succeeded. By nesting 
       // the using block for connection2 inside that of connection1, you 
       // conserve server and network resources as connection2 is opened 
       // only when there is a chance that the transaction can commit. 
       using (SqlConnection connection2 = new SqlConnection(connectString2)) 
       { 
        // The transaction is escalated to a full distributed 
        // transaction when connection2 is opened. 
        connection2.Open(); 

        // Execute the second command in the second database. 
        returnValue = 0; 
        SqlCommand command2 = new SqlCommand(commandText2, connection2); 
        returnValue = command2.ExecuteNonQuery(); 
        writer.WriteLine("Rows to be affected by command2: {0}", returnValue); 
       } 
      } 

      // The Complete method commits the transaction. If an exception has been thrown, 
      // Complete is not called and the transaction is rolled back. 
      scope.Complete(); 
     } 
    } 
    catch (TransactionAbortedException ex) 
    { 
     writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message); 
    } 
    catch (ApplicationException ex) 
    { 
     writer.WriteLine("ApplicationException Message: {0}", ex.Message); 
    } 

    // Display messages. 
    Console.WriteLine(writer.ToString()); 

    return returnValue; 
} 

只是一個想法,但你也許能夠使用TransactionAbortedException抓來獲得實際的錯誤,忽略交易數量不匹配的警告。

+0

@KM,你在樣本過程的早期有'IF @@ trancount <0'。 @@ trancount可以否定嗎?不應該;這是'IF @@ trancount = 0'? – 2010-12-07 20:37:59

+0

@Charles Bretana,你是對的,它是一個類型o。我會修復它... – 2010-12-07 21:38:07

+0

@KM,Thx!它在我的測試sproc中與變化一起工作,但是自從4月/ 5月以來這一直沒有受到干擾......所以我的自然假設是我錯過了一些東西......我並不完全確定這種方式或其他......快樂假期! – 2010-12-08 00:00:56

1

您應該使用try catch

BEGIN TRANSACTION --SqlTransaction 
BEGIN TRY 
    SELECT 1/0 
    COMMIT TRANSACTION --SqlTransaction 
    RETURN 0 
END TRY 
BEGIN CATCH 
    ROLLBACK TRANSACTION --SqlTransaction 
    RETURN -1 
END CATCH 

而且這個問題要回答有關的TransactionScope和回滾問題 How does TransactionScope roll back transactions?

+0

我忘了提,這已經爲SQL 2000年工作nd 2005年,但即使是2005年,if語句和try catch語句塊之間的區別也是如此。 Store Procedure獨立工作,即在查詢窗口中運行時。 – Mike 2010-04-26 13:09:49

+0

問題是,零除零錯誤是你的SP崩潰。 try-catch讓你的代碼在不離開SP的情況下失敗。在錯誤發生後,在IF語句中SP退出。 – Glennular 2010-04-26 13:53:22

+0

我不相信,因爲如果我運行上面的sql,我得到-1的結果是在if語句的返回語句中。 – Mike 2010-04-26 15:08:03

-1

我知道這是一個令人難以置信的平凡的建議,但將不是一個好的解決辦法是,以防止被零除擺在首位?幾乎所有的DML操作(插入,選擇,更新)都可以重寫,以避免使用CASE語句除以零。

+1

正確。但除以零是一個例子,這就是爲什麼我選擇硬編碼它。問題是捕獲錯誤和使用事務作用域對象的正確方法。 – Mike 2010-04-30 12:39:36

12

請勿使用中的交易您的C#代碼 sprocs。一個就足夠了。幾乎總是應該是你的C#代碼,只知道它應該拒絕或提交整個數據庫的哪些更新。

+2

我不確定我是否同意這一點。這可能是你正在編寫一個存儲過程api,目的是被許多用戶使用。作爲api的作者,你會知道什麼存儲過程需要比客戶更好地處理。 (當@@ trancount爲0時,它可能像一個raiserror一樣普通。) – Paul 2015-01-15 01:45:14

2

如果您必須支持SQL Server 2000,請使用TransactionScope讓您的生活更輕鬆。不過,請查看底部爲何存在限制。

TRY/CATCH之前的SQL錯誤處理是錯誤的。由KM公佈的Erland的文章解釋了聲明/範圍/批處理錯誤,使其如此。基本上,代碼可能只是停止執行,你留下鎖等行。

這是上面發生的事情,所以你的回滾不運行,所以你得到有關交易計數的錯誤226。

如果您僅支持SQL Server 2005+,請使用TRY/CATCH捕獲所有錯誤,並使用SET XACT_ABORT ON。 TRY/CATCH使SQL Server更具彈性,並捕獲所有運行時錯誤。 SET XACT_ABORT ON還抑制錯誤226,因爲它自動發佈回滾確保釋放所有鎖。

BTW:

選擇1/0是爲什麼你應該使用SQL的錯誤處理一個很好的例子。

使用DataAdapter來填補

  • 從SELECT 1/0一個存儲過程數據表 - >沒有錯誤被困
  • 數據集從一個存儲過程使用SELECT 1/0 - >錯誤被困

SQL TRY/CATCH將處理這個...

0
public string ExecuteReader(string SqlText) 
{ 
    SqlCommand cmd; 
    string retrunValue = ""; 
    try 
    { 
     c.Open(); 
     cmd = new SqlCommand(); 
     cmd.CommandType = CommandType.Text;     
     cmd.Connection = c; 
     cmd.CommandText = SqlText; 
     retrunValue = Convert.ToString(cmd.ExecuteScalar()); 
     c.Close(); 
    } 
    catch (Exception SqlExc) 
    { 
     c.Close(); 
     throw SqlExc; 

    } 
    return (retrunValue); 
}