Как повторно вызвать то же исключение в SQL Server

86

Я хочу повторно вызвать то же исключение в SQL Server, которое только что произошло в моем блоке try. Я могу отправить такое же сообщение, но хочу выдать ту же ошибку.

BEGIN TRANSACTION
    BEGIN TRY
        INSERT INTO Tags.tblDomain (DomainName, SubDomainId, DomainCode, Description)
            VALUES(@DomainName, @SubDomainId, @DomainCode, @Description)
        COMMIT TRANSACTION
    END TRY
    
    BEGIN CATCH
        declare @severity int; 
        declare @state int;

        select @severity=error_severity(), @state=error_state();

        RAISERROR(@@Error,@ErrorSeverity,@state);
        ROLLBACK TRANSACTION
    END CATCH

RAISERROR(@@Error, @ErrorSeverity, @state);

В этой строке будет отображаться ошибка, но мне нужна такая функциональность. Это вызывает ошибку с номером ошибки 50000, но я хочу, чтобы был выдан номер ошибки, которую я передаю @@error,

Я хочу зафиксировать эту ошибку на интерфейсе пользователя.

т.е.

catch (SqlException ex)
{
    if ex.number==2627
    MessageBox.show("Duplicate value cannot be inserted");
}

Я хочу эту функциональность. чего нельзя достичь с помощью raiseerror. Я не хочу отображать пользовательское сообщение об ошибке в конце.

RAISEERROR должен возвращать указанную ниже ошибку, когда я передаю ErrorNo, чтобы его бросили в catch

Msg 2627, Level 14, State 1, Procedure spOTest_DomainInsert,

Строка 14 Нарушение ограничения UNIQUE KEY 'UK_DomainCode'. Невозможно вставить повторяющийся ключ в объект Tags.tblDomain. Заявление было прекращено.

РЕДАКТИРОВАТЬ:

В чем может быть недостаток неиспользования блока try catch, если я хочу, чтобы исключение обрабатывалось во внешнем интерфейсе, учитывая, что хранимая процедура содержит несколько запросов, которые необходимо выполнить?

Шантану Гупта
источник

Ответы:

120

Вот полностью функциональный образец чистого кода для отката ряда операторов в случае возникновения ошибки и сообщения об ошибке.

begin try
    begin transaction;

    ...

    commit transaction;
end try
begin catch
    if @@trancount > 0 rollback transaction;
    throw;
end catch

До SQL 2012

begin try
    begin transaction;
    
    ...
    
    commit transaction;
end try
begin catch
    declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
    select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
    if @@trancount > 0 rollback transaction;
    raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
end catch
Бен Грипка
источник
8
Я использовал это в середине хранимой процедуры и обнаружил, что она продолжит выполнение после raiserror, что отличается от того, как C # завершает работу после throw. Поэтому я добавил returnвнутри, catchпотому что хотел соответствовать этому поведению.
Brian J
@BogdanBogdanov Я откатил ваше редактирование, потому что смысл этого кода должен быть минимальным и не умалять реальный код, введенный вместо ...
Бен Грипка
Хорошо, нет проблем, @Ben Gripka. Стараюсь, чтобы на экране было удобнее читать. Спасибо, что указали причину отката.
Богдан Богданов
1
@BrianJ: обычно остановка выполнения зависит от серьезности исходной ошибки. Если серьезность> = 11, выполнение должно остановиться. Это действительно странно, потому что ошибка повышения внутри блока catch с серьезностью> = 11 больше не останавливает выполнение. Ваше наблюдение очень хорошее, и оно показывает, насколько мертв мозговой сервер sql, по крайней мере, 2008r2. Более новые версии кажутся лучше.
Коста,
1
@costa См. RAISERROR()документы . Уровень серьезности ≥11 переходит к CATCHблоку, только если он находится внутри TRYблока. Итак, вы должны иметь BEGIN TRY…END CATCHкод, если хотите, RAISERROR()чтобы он влиял на управление потоком.
binki
137

SQL 2012 вводит оператор throw:

http://msdn.microsoft.com/en-us/library/ee677615.aspx

Если оператор THROW указан без параметров, он должен появиться внутри блока CATCH. Это приводит к возникновению пойманного исключения.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Майкл
источник
2
Осторожно, похоже, что это решение работает только с sql server 2012 и выше: msdn.microsoft.com/en-us/library/ee677615.aspx
Ади,
3
@BogdanBogdanov Чтобы вы могли зарегистрировать ошибку, возможно, обработать некоторые ситуации, но если вы не можете, тогда вы захотите повторно выбросить ошибку, чтобы у любого более высокого уровня try / catch был шанс обработать ее
Роберт Макки,
Да, @ Роберт МакКи. Я понимаю это. Извините, что забыл удалить этот комментарий.
Богдан Богданов
3
Эта точка с запятой в ROLLBACKстроке важна! Без него вы можете получить SQLException: Cannot roll back THROW.
idontevenseethecode
5

Повторное отображение внутри блока CATCH (код до SQL2012, используйте оператор THROW для SQL2012 и более поздних версий):

DECLARE
    @ErrorMessage nvarchar(4000) = ERROR_MESSAGE(),
    @ErrorNumber int = ERROR_NUMBER(),
    @ErrorSeverity int = ERROR_SEVERITY(),
    @ErrorState int = ERROR_STATE(),
    @ErrorLine int = ERROR_LINE(),
    @ErrorProcedure nvarchar(200) = ISNULL(ERROR_PROCEDURE(), '-');
SELECT @ErrorMessage = N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 'Message: ' + @ErrorMessage;
RAISERROR (@ErrorMessage, @ErrorSeverity, 1, @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine)
Нземин
источник
4

Я думаю, ваш выбор:

  • Не поймите ошибку (пусть она пузырится)
  • Поднять кастомный

В какой-то момент SQL, вероятно, представит команду reraise или возможность отлавливать только определенные ошибки. Но пока воспользуйтесь обходным путем. Сожалею.

Роб Фарли
источник
7
в sql 2012 вы можете повторно вызвать исключение, используя новое ключевое слово THROW
sergiom
5
Да. Конечно, когда задавали этот вопрос, этого не было.
Роб Фарли
Было бы важнее поймать и выбросить новую ошибку, чем не поймать ее и позволить ей «всплыть», потому что вам, вероятно, потребуются некоторые действия по очистке, исправлению и закрытию для правильной обработки исключения. Очевидным примером может быть закрытие и удаление курсора. Другими примерами могут быть выполнение процедуры регистрации или сброс некоторых данных.
Энтони Бут
1

Вы не можете: только движок может выдавать ошибки меньше 50000. Все, что вы можете сделать, это создать исключение, которое выглядит так ...

Смотрите мой ответ здесь, пожалуйста

Здесь спрашивающий использовал транзакции на стороне клиента, чтобы делать то, что он хотел, что, на мой взгляд, немного глупо ...

gbn
источник
0

Хорошо, это обходной путь ... :-)

DECLARE @Error_Number INT
BEGIN TRANSACTION 
    BEGIN TRY
    INSERT INTO Test(Id, Name) VALUES (newID(),'Ashish') 
    /* Column 'Name' has unique constraint on it*/
    END TRY
    BEGIN CATCH

            SELECT ERROR_NUMBER()
            --RAISERROR (@ErrorMessage,@Severity,@State)
            ROLLBACK TRAN
    END CATCH

Если вы заметили блок catch, он не вызывает ошибку, а возвращает фактический номер ошибки (а также откатывает транзакцию). Теперь в вашем .NET-коде вместо того, чтобы перехватывать исключение, если вы используете ExecuteScalar (), вы получаете фактический номер ошибки, который хотите, и соответствующий номер.

int errorNumber=(int)command.ExecuteScalar();
if(errorNumber=<SomeNumber>)
{
    MessageBox.Show("Some message");
}

Надеюсь это поможет,

РЕДАКТИРОВАТЬ: - Просто примечание: если вы хотите получить количество затронутых записей и пытаетесь использовать ExecuteNonQuery, вышеуказанное решение может не сработать для вас. В противном случае, думаю, подойдет то, что вам нужно. Дайте мне знать.

Ашиш Гупта
источник
@Ashish Gupta: Спасибо за помощь, но мне нужно исключение, которое должно быть выброшено из базы данных во внешний интерфейс, в противном случае у меня открыто много вариантов, таких как print error_number (), return error_number и предложенный 1 u
Шантану Гупта
0

Чтобы остановить выполнение хранимой процедуры после того, как произошла ошибка, и передать ошибку обратно вызывающей программе, нужно следовать каждому оператору, который может вызвать ошибку, с помощью этого кода:

If @@ERROR > 0
Return

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

Этот тип обработки ошибок параллелен (до .Net) Visual Basic 6. С нетерпением жду команды Throw в SQL Server 2012.

Чак Бевитт
источник
0

Учитывая, что вы еще не перешли в 2012 год, один из способов реализовать всплытие исходного кода ошибки - использовать часть текстового сообщения исключения, которое вы (повторно) выбрасываете из блока catch. Помните, что он может содержать некоторую структуру, например текст XML для кода вызывающего абонента, который будет анализироваться в его блоке catch.

Юрий Макасюк
источник
0

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

CREATE PROCEDURE usp_Execute_SQL_Within_Transaction
(
    @SQL nvarchar(max)
)
AS

SET NOCOUNT ON

BEGIN TRY
    BEGIN TRANSACTION
        EXEC(@SQL)
    COMMIT TRANSACTION
END TRY

BEGIN CATCH
    DECLARE @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int
    SELECT @ErrorMessage = N'Error Number: ' + CONVERT(nvarchar(5), ERROR_NUMBER()) + N'. ' + ERROR_MESSAGE() + ' Line ' + CONVERT(nvarchar(5), ERROR_LINE()), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE()
    ROLLBACK TRANSACTION
    RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState)
END CATCH

GO

-- Test it
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'SELECT 1/0; SELECT 2'
EXEC usp_Execute_SQL_Within_Transaction @SQL = 'EXEC usp_Another_SP'
Сергей
источник
-2

С точки зрения дизайна, какой смысл генерировать исключения с исходными номерами ошибок и настраиваемыми сообщениями? В некоторой степени это нарушает интерфейсный контракт между приложениями и базой данных. Если вы хотите перехватывать исходные ошибки и обрабатывать их в коде более высокого уровня, не обрабатывайте их в базе данных. Затем, когда вы поймаете исключение, вы можете изменить сообщение, представленное пользователю, на все, что захотите. Я бы не стал этого делать, потому что это делает код вашей базы данных «неправильным». Как говорили другие, вы должны определить набор собственных кодов ошибок (выше 50000) и вместо этого выбросить их. Затем вы можете обрабатывать проблемы целостности («Повторяющиеся значения не допускаются») отдельно от потенциальных бизнес-проблем - «Недопустимый почтовый индекс», «Не найдено строк, соответствующих критериям» и т. Д.

Петр Родак
источник
9
Какой смысл генерировать исключения с исходными номерами ошибок и пользовательскими сообщениями? Предположим, вы хотите обработать одну или две определенные (ожидаемые) ошибки непосредственно в блоке catch, а остальные оставить для более высоких уровней. Таким образом, вам нужно иметь возможность повторно генерировать исключения, которые вы не обрабатывали ... желательно, не прибегая к отчетности и обработке ошибок каким-либо другим, особым способом.
Jenda
1
В дополнение к тому, что объяснил @Jenda, мне нравится использовать try-catch, чтобы гарантировать, что выполнение кода не продолжается после исключения, во многом как в do в C # : try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }, где throw;просто вызовет исходное исключение с его исходным контекстом.
R. Schreurs
Я отлавливаю ошибки и повторно выдаю пользовательские сообщения об ошибках в SQL, чтобы добавить подробности, описывающие, в какой строке произошла ошибка, или другие детали (например, данные, которые пытаются вставить), чтобы помочь мне отследить ошибку позже.
Рассел Хэнкинс,