TransactionScope и транзакции - PullRequest
       15

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

17 голосов
/ 24 апреля 2010

В своем коде 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()
         }

EDIT:

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

Ответы [ 6 ]

22 голосов
/ 04 мая 2010

Произошло масштабное обновление обработки ошибок в SQL Server 2005. Эти статьи довольно обширны: Обработка ошибок в SQL 2005 и более поздних версиях Erland Sommarskog и Обработка ошибок в SQL 2000 - a Фон Эрланда Соммарского

Лучший способ - что-то вроде этого:

Создайте свою хранимую процедуру как:

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 откатывается все самое внешнее НАЧИНАЕТСЯ СДЕЛКА (если вы не использовали довольно экзотическая SAVE TRANSACTION), и силы @@ transcount до 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

Делая это, вы вводите команды транзакции, только если вы еще не в транзакции. Если вы закодируете все свои процедуры таким образом, только процедура или код C #, который выдает BEGIN TRANSACTION, фактически выдает 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;
}

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

13 голосов
/ 08 мая 2010

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

2 голосов
/ 09 мая 2010

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

Обработка ошибок SQL перед ошибкой TRY / CATCH.Статья Эрланда, опубликованная 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 для заполнения

  • объекта Datatable из хранимого процесса с помощью SELECT 1/0 -> без ошибок
  • a DataSetиз хранимого процесса с SELECT 1/0 -> ошибка в ловушке

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

1 голос
/ 24 апреля 2010

Вы должны использовать попытку поймать

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 откатывает транзакции?

0 голосов
/ 08 января 2013
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);
}
0 голосов
/ 30 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...