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

В моем коде С# я использую 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

Вызов из кода С# Я получаю дополнительное сообщение об ошибке

Деление на нулевую ошибку. Счет транзакции после EXECUTE указывает на отсутствие привязки COMMIT или ROLLBACK TRANSACTION. Предыдущий счетчик = 1, текущий счетчик = 0.

Если я дам транзакции sql имя, то

Нельзя отменить SqlTransaction. Не найдено ни одной транзакции или точки сохранения этого имени.  Счет транзакции после EXECUTE указывает, что COMMIT или ROLLBACK Отсутствует инструкция TRANSACTION. Предыдущий счетчик = 1, текущий счетчик = 2.

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

С# просто

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

             scope.Commit()
         }

EDIT:

Код sql должен работать в течение 2000 и 2005 годов

Ответы

Ответ 1

В SQL Server 2005 произошла массовая модернизация обработки ошибок. Эти статьи довольно обширны: Обработка ошибок в SQL 2005 и более поздних версиях Erland Sommarskog и Обработка ошибок в SQL 2000 - справочная информация 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 обратно. extra messages, о котором вы говорите, вызваны тем, как обрабатываются откаты с помощью @@trancount:

из http://www.sommarskog.se/error-handling-I.html#trancount

@@trancount - глобальная переменная, которая отражает уровень вложенных сделки. Каждая НАЧАЛЬНАЯ СДЕЛКА увеличивается на @@trancount на 1, и каждый COMMIT TRANSACTION уменьшается @@trancount by 1. Ничто на самом деле до тех пор, пока @@trancount не достигнет 0. ROLLBACK TRANSACTION откат все к самому внешнему НАЧАТЬ TRANSACTION (если вы не использовали довольно экзотическая SAVE TRANSACTION), и force @@trancount to 0, отношение предыдущее значение.

Когда вы выходите из хранимой процедуры, если @@trancount не имеет того же ценность, как это было в случае процедуры началось выполнение SQL Server ошибка 266. Эта ошибка не возникает, хотя, если процедура вызывается от триггера, напрямую или косвенно. Он также не поднимается, если вы работаете с SET IMPLICIT ОПЕРАЦИИ НА

Если вы не хотите получать предупреждение о том, что количество транзакций не соответствует, вам нужно только одну транзакцию открыть в любой момент времени. Вы делаете это, создавая всю свою процедуру следующим образом:

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, на самом деле выдает COMMIT/ROLLBACK, и подсчет транзакций всегда будет соответствовать (вы не получите ошибку).

в С# из Документация класса 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;
}

Просто подумайте, но вы можете использовать catch TransactionAbortedException для получения фактической ошибки и игнорировать предупреждение о несоответствии количества транзакций.

Ответ 2

Не используйте транзакции как в коде С#, так и в sprocs. Одного достаточно. Который почти всегда должен быть вашим С# -кодом, только он знает, какой набор обновлений для dbase должен быть отклонен или передан целиком.

Ответ 3

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

Обработка ошибок SQL до того, как TRY/CATCH будет правдивым. В статье Erland, опубликованной KM, объясняются ошибки прерывания /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 для заполнения

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

SQL TRY/CATCH будет иметь дело с этим...

Ответ 4

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

Ответ 5

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

Ответ 6

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