Обработка исключений при откате SqlTransaction - PullRequest
4 голосов
/ 03 июня 2009

У меня есть две хранимые процедуры, которые я хочу выполнить, завернутые в транзакцию. По разным причинам мне нужно обрабатывать транзакции в коде моего приложения, а не в базе данных.

На данный момент мой код выглядит так:

try
{
    using (SqlConnection conn = Connection())
    {
        conn.Open();

        using (SqlTransaction sqlTrans = conn.BeginTransaction())
        {
            try
            {
                using (SqlCommand cmd1 = new SqlCommand("Stored_Proc_1", conn, sqlTrans))
                {
                    cmd1.CommandType = CommandType.StoredProcedure;
                    cmd1.ExecuteNonQuery();
                }

                using (SqlCommand cmd2 = new SqlCommand("Stored_Proc_2", conn, sqlTrans))
                {
                    cmd2.CommandType = CommandType.StoredProcedure;
                    cmd2.ExecuteNonQuery();
                }

                sqlTrans.Commit();
            }
            catch
            {
                    sqlTrans.Rollback();

                    throw;
            }

        }

        conn.Close();
    }
}

catch (SqlException ex)
{
  // exception handling and logging code here...
}

Когда один из сохраненных процессов вызывает ошибку, я вижу сообщение об исключении:

Error message from raiserror within stored procedure.
Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0.

Это имеет смысл, потому что при первом улове транзакция еще не была отменена.

Но я хочу "чистую" ошибку (без сообщения о количестве транзакций - мне это не интересно, потому что я am откатываю транзакцию) для моего кода обработки исключений. Есть ли способ, которым я могу реструктурировать свой код для достижения этой цели?

EDIT:

Базовая структура моих хранимых процедур выглядит следующим образом:

create proc Stored_Proc_1
as

set nocount on

begin try
    begin transaction 

        raiserror('Error raised by Stored_Proc_1', 16, 1)       

    commit

end try
begin catch  
    if (@@trancount > 0) rollback   

    declare @ErrMsg nvarchar(4000), @ErrSeverity int, @ErrProc sysname, @ErrLine varchar(10)
    select @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY(), @ErrProc = ERROR_PROCEDURE(), @ErrLine = ERROR_LINE()

    -- log the error 
    -- sql logging code here...

    raiserror(@ErrMsg, @ErrSeverity, 1) 
end catch

UPDATE: Я убрал обработку транзакций из своих хранимых процедур, и это, похоже, решило проблему. Очевидно, я делал это неправильно, но я все еще хотел бы знать, как сделать это правильно. Является ли удаление транзакций из хранимых процедур лучшим решением?

Ответы [ 4 ]

5 голосов
/ 03 июня 2009

Ну, conn.Close() может пойти в любом случае - он закроется using (если подумать, странно, что мы только Close() это после исключения).

Делает ли какая-либо из ваших хранимых процедур какой-либо код транзакции внутри себя (который не откатывается / не фиксируется)? Похоже , что в чем проблема ...? Во всяком случае, сообщение об ошибке подсказывает мне, что одна из хранимых процедур выполняет COMMIT, даже если она не начала транзакцию - возможно, из-за (неправильного) подхода:

-- pseduo-TSQL
IF @@TRANCOUNT = 0 BEGIN TRAN
-- ...
IF @@TRANCOUNT > 0 COMMIT TRAN -- or maybe = 1

(если вы делаете условные транзакции в TSQL, вы должны отслеживать (с помощью флага bool), создали ли вы транзакцию - и только COMMIT, если вы это сделали)

Другой вариант - использовать TransactionScope - проще в использовании (вам не нужно настраивать его для каждой команды и т. Д.), Но немного менее эффективно:

using(TransactionScope tran = new TransactionScope()) {
    // create command, exec sp1, exec sp2 - without mentioning "tran" or
    // anything else transaction related

    tran.Complete();
}

(обратите внимание, что отката нет и т. Д .; Dispose() (через using) выполнит откат, если потребуется.

4 голосов
/ 03 июня 2009

Не выполняйте транзакции в вашей базе данных / хранимых процедурах, если вы делаете это в своем приложении! Это наверняка просто создаст путаницу. Выберите слой и придерживайтесь его. Убедитесь, что у вас есть хорошая нормализованная база данных и исключения должны просачиваться вверх.

1 голос
/ 03 июня 2009

Я согласен с Марком, что проблема, скорее всего, в самих хранимых процедурах. Есть довольно интересная статья, в которой изложены несколько вопросов здесь .

0 голосов
/ 11 августа 2016

Если хранимая процедура содержит код, подобный следующему:

BEGIN TRY
    SET @now = CAST(@start AS datetime2(0))
END TRY
BEGIN CATCH
    SET @now = CURRENT_TIMESTAMP
END CATCH

и вы передаете, например, «сейчас» как @start, CAST в попытке завершится неудачей. Это помечает транзакцию как откат, только если сама ошибка была захвачена и обработана. Поэтому, пока вы не получите никаких исключений из приведенного выше кода, транзакция не может быть зафиксирована. Если ваши хранимые процедуры имеют такой код, его нужно переписать, чтобы избежать попытки / улова.

...