2010-04-23 128 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); 
}