TransactionScope и транзакции

В моем коде 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#, я получаю дополнительное сообщение об ошибке

Разделитесь на нулевую ошибку, с которой встречаются.
Количество транзакции после ВЫПОЛНЯЕТСЯ, указывает, что ФИКСАЦИЯ или ROLLBACK TRANSACTION tatement отсутствуют. Предыдущее количество = 1, текущее количество = 0.

Если я даю sql транзакции имя затем

Не может откатывать SqlTransaction. Никакая транзакция или точка сохранения того имени не были найдены. Количество транзакции после ВЫПОЛНЯЕТСЯ, указывает, что оператор COMMIT или ROLLBACK TRANSACTION отсутствует. Предыдущее количество = 1, текущее количество = 2.

несколько раз кажется, что количество повышается, пока приложение полностью не выходит

c# справедлив

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

             scope.Commit()
         }

Править:

Код SQL должен работать на 2000 и 2005

17
задан Mike 26 April 2010 в 13:06
поделиться

5 ответов

В SQL Server 2005 было проведено масштабное обновление обработки ошибок. Эти статьи довольно обширны: Error Handling in SQL 2005 and Later by Erland Sommarskog и Error 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 и выше. Без использования блоков TRY-CATCH в SQL Server 2005 вам будет очень трудно удалить все сообщения, которые SQL Server посылает обратно. дополнительные сообщения, о которых вы говорите, вызваны характером обработки откатов с помощью @@trancount:

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

@@trancount является глобальной переменной, которая отражает уровень вложенных транзакций. Каждая транзакция BEGIN TRANSACTION увеличивает @@trancount на 1, а каждая COMMIT TRANSACTION уменьшает @@trancount на 1. Ничего не фиксируется до тех пор. пока @@trancount не достигнет 0. ТРАНЗАКЦИЯ ROLLBACK откатывает все к крайнему 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

Таким образом, команды транзакции выдаются только в том случае, если вы еще не находитесь в транзакции. Если вы закодируете все свои процедуры таким образом, только та процедура или код C#, которая выдает команду BEGIN TRANSACTION, будет действительно выдавать 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 catch для получения фактической ошибки и игнорирования предупреждения о несоответствии количества транзакций.

22
ответ дан 30 November 2019 в 11:43
поделиться

Вам следует использовать 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 Как работает TransactionScope обратные транзакции?

1
ответ дан 30 November 2019 в 11:43
поделиться

Я знаю, что это невероятно обыденное предложение, но разве не было бы хорошим решением вообще предотвратить деление на ноль? Практически все операции DML (вставка, выбор, обновление) можно переписать, чтобы избежать деления на нули, с помощью операторов CASE.

-1
ответ дан 30 November 2019 в 11:43
поделиться

Не используйте транзакции в обоих ваших C# кодахи пробросах. Достаточно одной. Почти всегда это должен быть ваш C# код, только он знает, какой набор обновлений базы данных должен быть отклонен или зафиксирован целиком.

13
ответ дан 30 November 2019 в 11:43
поделиться

Если вам приходится поддерживать SQL Server 2000, используйте TransactionScope, чтобы облегчить себе жизнь. Однако смотрите внизу, почему он имеет ограничения.

Обработка ошибок SQL до TRY/CATCH является нерациональной. В статье Эрланда, опубликованной KM, объясняются ошибки прерывания statement/scope/batch, которые делают это так. По сути, код может просто прекратить выполнение, и вы останетесь с блокировками на строках и т.д.

Это то, что происходит выше, поэтому ваш откат не выполняется, и вы получаете ошибку 226 о количестве транзакций.

Если вы поддерживаете только SQL Server 2005+, то используйте TRY/CATCH, который ловит все ошибки, а также используйте SET XACT_ABORT ON. TRY/CATCH делает SQL Server намного более устойчивым и отлавливает все ошибки во время выполнения. SET XACT_ABORT ON также подавляет ошибку 226, поскольку автоматически делает откат и обеспечивает освобождение всех блокировок.

BTW:

SELECT 1/0 является отличным примером того, почему вы должны использовать обработку ошибок SQL.

Используйте DataAdapter для заполнения

  • Datatable из хранимой процедуры с SELECT 1/0 -> ошибка не поймана
  • DataSet из хранимой процедуры с SELECT 1/0 -> ошибка поймана

SQL TRY/CATCH справится с этим...

2
ответ дан 30 November 2019 в 11:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: