一尘不染

交易范围和交易

sql

在我的C#代码中,我使用的是TransactionScope,因为有人告诉我不要依赖我的sql程序员将始终使用事务,因此我们是负责任的。

话说回来

看起来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语句。 先前计数= 1,当前计数= 0。

如果我给sql事务起一个名字

无法回滚SqlTransaction。 找不到该名称的事务或保存点。EXECUTE之后的事务计数表明缺少COMMIT或ROLLBACK
TRANSACTION语句。上一个计数= 1,当前计数= 2。

有时似乎计数增加了,直到应用程序完全退出

C#只是

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

             scope.Commit()
         }

编辑:

SQL代码必须适用于2000年和2005年


阅读 142

收藏
2021-03-17

共1个答案

一尘不染

有一个大规模升级到错误的SQL Server 2005中处理这些文章是相当广泛: 在SQL
2005和后来的厄兰Sommarskog错误处理
错误处理SQL2000鈥由厄兰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(除非您已经使用了相当奇特的保存交易),并将@@ 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

这样,仅当您尚未进行事务处理时,才发出事务处理命令。如果以这种方式对所有过程进行编码,则只有发出BEGINTRANSACTION的过程或C#代码才会真正发出COMMIT /ROLLBACK,并且事务计数将始终匹配(不会出错)。

在C#中来自TransactionScope类文档

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

只是一个想法,但是您可以使用TransactionAbortedExceptioncatch来获取实际错误,而忽略事务计数不匹配警告。

2021-03-17