TransactionScope和Transactions

在我的C#代码中,我使用TransactionScope,因为我被告知不要依赖我的sql程序员将始终使用事务,我们负责和yada yada。

话说回来

它看起来像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并返回-1

从C#代码调用我得到一个额外的错误消息

遇到零除错误。
EXECUTE之后的事务计数表示缺少COMMIT或ROLLBACK TRANSACTION tatement。 先前的计数= 1,当前计数= 0。

如果我给sql事务一个名字然后

无法回滚SqlTransaction。 未找到该名称的任何事务或保存点。 EXECUTE之后的事务计数表示缺少COMMIT或ROLLBACK TRANSACTION语句。 先前的计数= 1,当前计数= 2。

有时似乎计数上升,直到应用程序完全退出

c#就是这样

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

编辑:

sql代码必须适用于2000和2005

SQL Server 2005中的error handling有了大规模升级。这些文章相当广泛: Erland Sommarskog在SQL 2005及更高版本中的 error handling和SQL 2000中的error handling – 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是一个全局变量,它反映了嵌套事务的级别。 每个BEGIN TRANSACTION将@@ trancount增加1,每个COMMIT TRANSACTION减少@@ trancount 1.在@@ trancount达到0之前,实际上没有任何内容.ROLLBACK TRANSACTION将所有内容回滚到最外面的BEGIN TRANSACTION(除非你使用了相当异乎寻常的东西) SAVE TRANSACTION),强制@@ trancount为0,关注前一个值。

退出存储过程时,如果@@ trancount与程序开始执行时的值不同,则SQL Server会引发错误266.但是,如果从触发器直接调用过程,则不会引发此错误。或间接的。 如果您使用SET IMPLICIT TRANSACTIONS ON运行,也不会引发此问题

如果您不希望收到有关事务计数不匹配的警告,则您只需要在任何时候打开一个事务。 您可以通过创建所有这样的过程来完成此操作:

 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,并且事务计数将始终匹配(您不会收到错误)。

在TransactionScope类文档的 C#中:

 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 catch来获取实际错误并忽略事务计数不匹配警告。

不要在C#代码 sprocs中使用事务。 一个就够了。 几乎总是应该是您的C#代码,只有它知道应该拒绝或提交dbase的哪些更新集。

如果您必须支持SQL Server 2000,请使用TransactionScope让您的生活更轻松。 但是,请在底部查看它有限制的原因。

在TRY / CATCH之前的SQLerror handling是错误的。 KM发布的Erland的文章解释了声明/范围/批量中止错误。 基本上,代码可能只是停止执行而你会留下行等锁。

这就是上面发生的事情,因此您的回滚不会运行,因此您会收到有关事务计数的错误226。

如果仅支持SQL Server 2005+,则使用TRY / CATCH捕获所有错误,并使用SET XACT_ABORT ON。 TRY / CATCH使SQL Server更具弹性并捕获所有运行时错误。 SET XACT_ABORT ON还会抑制错误226,因为它会自动发出回滚确保释放所有锁。

BTW:

SELECT 1/0是您应该使用SQLerror handling的一个很好的例子。

使用DataAdapter填充

  • 来自存储过程的数据表,带有SELECT 1/0 – >没有错误被捕获
  • 来自存储过程的DataSet,带有SELECT 1/0 – >错误陷阱

SQL TRY / CATCH将处理这个……

你应该使用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和Rollbacks的问题.ExchangeScope 如何回滚交易?

 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); } 

我知道这是一个令人难以置信的平凡建议,但是首先要防止零除以后不是一个好的解决方案吗? 几乎所有的DML操作(插入,选择,更新)都可以重写,以避免通过使用CASE语句除以零。