Como relançar a mesma exceção no SQL Server

86

Quero relançar a mesma exceção no SQL Server que acabou de ocorrer no meu bloco try. Consigo lançar a mesma mensagem, mas desejo lançar o mesmo erro.

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

Esta linha mostrará erro, mas eu quero uma funcionalidade parecida com isso. Isso gera um erro com o número de erro 50000, mas quero que seja emitido o número do erro que estou passando @@error,

Eu quero capturar esse erro não no frontend.

ie

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

Eu quero essa funcionalidade. que não pode ser alcançado usando raiseerror. Não quero dar mensagem de erro personalizada no back-end.

RAISEERROR deve retornar o erro mencionado abaixo quando eu passar ErrorNo para ser lançado em catch

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

Linha 14 violação da restrição UNIQUE KEY 'UK_DomainCode'. Não é possível inserir uma chave duplicada no objeto 'Tags.tblDomain'. A instrução foi encerrada.

EDITAR:

Qual pode ser a desvantagem de não usar o bloco try catch se eu quiser que a exceção seja tratada no front-end, considerando que o procedimento armazenado contém várias consultas que precisam ser executadas?

Shantanu Gupta
fonte

Respostas:

120

Aqui está um exemplo de código limpo totalmente funcional para reverter uma série de instruções se ocorrer um erro e relatar a mensagem de erro.

begin try
    begin transaction;

    ...

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

Antes do 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
Ben Gripka
fonte
8
Eu estava usando isso no meio de um procedimento armazenado e descobri que ele continuaria a ser executado depois raiserror, o que é diferente de como c # sai após a throw. Então, adicionei um returndentro do catchporque queria corresponder a esse comportamento.
Brian J,
@BogdanBogdanov Reverti sua edição porque o objetivo desse código é ser mínimo e não prejudicar o código real inserido no lugar do ...
Ben Gripka
Ok, sem problemas, @Ben Gripka. Tento torná-lo mais legível na tela. Obrigado por apontar o motivo para fazer rollback.
Bogdan Bogdanov
1
@BrianJ: normalmente, se a execução é interrompida ou não depende da gravidade do erro original. Se a gravidade for> = 11, a execução deve parar. É realmente estranho, porque o raiserror dentro do bloco catch com uma severidade> = 11 não para mais a execução. Sua observação é muito boa e mostra como o sql server tem morte cerebral, pelo menos 2008r2. As versões mais recentes parecem melhores.
costa
1
@costa Veja RAISERROR()a documentação de . A gravidade de ≥11 somente salta para um CATCHbloco se estiver dentro de um TRYbloco. Portanto, você deve ter BEGIN TRY…END CATCHcódigo próximo se quiser RAISERROR()afetar o controle de fluxo.
binki
137

SQL 2012 apresenta a instrução throw:

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

Se a instrução THROW for especificada sem parâmetros, ela deve aparecer dentro de um bloco CATCH. Isso faz com que a exceção detectada seja gerada.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Michael
fonte
2
Cuidado, parece que esta solução funciona apenas a partir do sql server 2012 e superior: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi
3
@BogdanBogdanov Para que você possa registrar o erro, possivelmente lidar com algumas situações, mas se não puder, então você deve relançar o erro para que qualquer tentativa / captura superior possa ter a chance de lidar com ele
Robert McKee
Sim, @Robert McKee. Eu descobri isso. Desculpe, esqueci de limpar este comentário.
Bogdan Bogdanov
3
Esse ponto-e-vírgula na ROLLBACKlinha é importante! Sem ele, você pode obter um SQLException: Cannot roll back THROW.
idontevenseethecode
5

Jogando novamente dentro do bloco CATCH (código pré-SQL2012, use a instrução THROW para SQL2012 e posterior):

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)
Nzeemin
fonte
4

Eu acho que suas escolhas são:

  • Não identifique o erro (deixe-o surgir)
  • Crie um personalizado

Em algum ponto, o SQL provavelmente apresentará um comando reraise ou a capacidade de detectar apenas alguns erros. Mas, por enquanto, use uma solução alternativa. Desculpe.

Rob Farley
fonte
7
no sql 2012, você pode gerar novamente uma exceção usando a nova palavra
sergiom
5
Sim. Claro, isso não estava disponível quando essa pergunta foi feita.
Rob Farley
Seria mais importante detectar e lançar um novo erro do que não detectá-lo e deixá-lo 'borbulhar', porque provavelmente você precisará de alguma limpeza, ação corretiva e atividades de encerramento para lidar com a exceção adequadamente. Um exemplo óbvio seria fechar e descartar um cursor. Outros exemplos podem ser para executar um procedimento de registro ou para redefinir alguns dados.
Antony Booth,
1

Você não pode: apenas o motor pode lançar erros menores que 50000. Tudo que você pode fazer é lançar uma exceção que parece com isso ...

Veja minha resposta aqui por favor

O questionador aqui usou transações do lado do cliente para fazer o que ele queria, o que eu acho um pouco bobo ...

gbn
fonte
0

Ok, esta é uma solução alternativa ... :-)

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

Se você observar o bloco catch, ele não está gerando o erro, mas retornando o número real do erro (e também iria reverter a transação). Agora, em seu código .NET, em vez de capturar a exceção, se você usar ExecuteScalar (), obtém o número do erro real que deseja e mostra o número apropriado.

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

Espero que isto ajude,

EDITAR: - Apenas uma observação, se você deseja obter o número de registros afetados e tentando usar ExecuteNonQuery, a solução acima pode não funcionar para você. Caso contrário, acho que seria adequado para o que você precisa. Avise-se me.

Ashish Gupta
fonte
@Ashish Gupta: Obrigado por ajuda, mas eu preciso que uma exceção seja lançada do banco de dados para o frontend, caso contrário, tenho muitas opções abertas como imprimir error_number (), retornar error_number e o 1 sugerido
Shantanu Gupta de
0

A maneira de interromper a execução em um procedimento armazenado após a ocorrência de um erro e enviá-lo de volta ao programa de chamada é seguir cada instrução que possa gerar um erro com este código:

If @@ERROR > 0
Return

Fiquei surpreso ao descobrir que a execução em um procedimento armazenado pode continuar após um erro - não perceber isso pode levar a alguns bugs difíceis de rastrear.

Este tipo de tratamento de erros é paralelo (pré. Net) ao Visual Basic 6. Ansioso pelo comando Throw no SQL Server 2012.

Chuck Bevitt
fonte
0

Dado que você ainda não mudou para 2012, uma maneira de implementar o borbulhamento do código de erro original é usar a parte da mensagem de texto da exceção que você está (re) lançando do bloco catch. Lembre-se de que ele pode conter alguma estrutura, por exemplo, texto XML para o código do chamador analisar em seu bloco catch.

Yuri Makassiouk
fonte
0

Você também pode criar um procedimento armazenado de wrapper para esses cenários quando desejar que a instrução SQL seja executada dentro da transação e alimentar o erro em seu código.

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'
Sergey
fonte
-2

Do ponto de vista do design, qual é o ponto de lançar exceções com números de erro originais e mensagens personalizadas? Até certo ponto, ele quebra o contrato de interface entre os aplicativos e o banco de dados. Se você deseja capturar erros originais e tratá-los em código superior, não os trate no banco de dados. Então, ao capturar uma exceção, você pode alterar a mensagem apresentada ao usuário para o que quiser. Eu não faria isso, porque torna o código do seu banco de dados hmm 'incorreto'. Como outros disseram, você deve definir um conjunto de seus próprios códigos de erro (acima de 50000) e jogá-los em seu lugar. Em seguida, você pode lidar com problemas de integridade ('Valores duplicados não são permitidos') separadamente de possíveis problemas de negócios - 'O código postal é inválido', 'Nenhuma linha foi encontrada correspondendo aos critérios' e assim por diante.

Piotr Rodak
fonte
9
Qual é o ponto de lançar exceções com números de erro originais e mensagens personalizadas? Suponha que você queira tratar um ou dois erros específicos (esperados) diretamente no bloco catch e deixar o resto para as camadas superiores. Assim, você precisa ser capaz de relançar as exceções que você não tratou ... de preferência sem ter que recorrer a relatar e tratar os erros de alguma outra forma especial.
Jenda
1
Além do que @Jenda explicou, gosto de usar try-catch para garantir que a execução do código não continue após uma exceção, muito parecido com do em C #:, try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }onde throw;apenas levantará a exceção original, com seu contexto original.
R. Schreurs
Eu pego erros e reenvio mensagens de erro personalizadas no SQL para adicionar detalhes que descrevem em qual linha o erro ocorreu ou outros detalhes (como os dados que estão tentando ser inseridos) para me ajudar a rastrear o erro mais tarde.
Russell Hankins