TransactionScope和Transactions

Mik*_*ike 17 c# sql 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
Run Code Online (Sandbox Code Playgroud)

如果我运行这个,我得到除以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()
         }
Run Code Online (Sandbox Code Playgroud)

编辑:

sql代码必须适用于2000和2005

KM.*_*KM. 22

有一个大规模升级到错误的SQL Server 2005中处理这些文章是相当广泛: 错误处理SQL 2005中,后来被厄兰Sommarskog错误处理SQL 2000 -由厄兰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
Run Code Online (Sandbox Code Playgroud)

但是,这仅适用于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
Run Code Online (Sandbox Code Playgroud)

通过执行此操作,您只会在事务中尚未发出事务命令.如果以这种方式编写所有过程,只有发出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;
}
Run Code Online (Sandbox Code Playgroud)

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


Han*_*ant 13

不要在使用交易双方你的C#代码,并在存储过程.一个就够了.几乎总是应该是你的C#代码,只有它知道dbase的哪些更新应该被拒绝或全部提交.

  • 我不确定我同意这一点.可能是您正在编写旨在供许多受众使用的存储过程api的情况.作为那个api作者,你会知道哪些存储过程需要比客户更好地进行交易.(当@@ trancount为0时,它可能像raiserror一样平凡.) (3认同)