Come rilanciare la stessa eccezione in SQL Server


86

Voglio rilanciare la stessa eccezione in SQL Server che si è appena verificata nel mio blocco try. Sono in grado di lanciare lo stesso messaggio ma voglio lanciare lo stesso errore.

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

Questa riga mostrerà un errore, ma voglio una funzionalità simile. Ciò solleva un errore con il numero di errore 50000, ma voglio che venga lanciato il numero di errore che sto passando @@error,

Voglio catturare questo errore no sul frontend.

cioè

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

Voglio questa funzionalità. che non può essere ottenuto utilizzando raiseerror. Non voglio fornire un messaggio di errore personalizzato al back-end.

RAISEERROR dovrebbe restituire l'errore sotto indicato quando passo ErrorNo da lanciare in catch

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

Riga 14 Violazione del vincolo UNIQUE KEY "UK_DomainCode". Impossibile inserire la chiave duplicata nell'oggetto "Tags.tblDomain". La dichiarazione è stata interrotta.

MODIFICARE:

Quale può essere lo svantaggio di non utilizzare il blocco try catch se voglio che l'eccezione venga gestita al frontend considerando che la stored procedure contiene più query che devono essere eseguite?

Risposte:


120

Di seguito è riportato un esempio di codice pulito completamente funzionale per eseguire il rollback di una serie di istruzioni se si verifica un errore e segnala il messaggio di errore.

begin try
    begin transaction;

    ...

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

Prima di 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
Lo stavo usando nel mezzo di una stored procedure e ho scoperto che avrebbe continuato a essere eseguito dopo raiserror, il che è diverso da come c # esce dopo un file throw. Quindi ho aggiunto un returnall'interno del catchperché volevo abbinare quel comportamento.
Brian J

@BogdanBogdanov Ho annullato la tua modifica perché il punto di questo codice deve essere minimo e non sminuire il codice reale inserito al posto del ...
Ben Gripka

Ok, nessun problema, @Ben Gripka. Cerco di renderlo più leggibile sullo schermo. Grazie per aver indicato il motivo per eseguire il rollback.
Bogdan Bogdanov,

1
@BrianJ: normalmente, se l'esecuzione si interrompe o meno dipende dalla gravità dell'errore originale. Se la gravità è> = 11, l'esecuzione dovrebbe interrompersi. È davvero strano, perché il raiserror all'interno del blocco catch con una gravità> = 11 non interrompe più l'esecuzione. La tua osservazione è molto buona e mostra quanto sia morto il cervello sql server, almeno 2008r2. Le versioni più recenti sembrano migliori.
costa

1
@costa Vedi RAISERROR()i documenti di . La gravità ≥11 salta a un CATCHblocco solo se si trova all'interno di un TRYblocco. Quindi è necessario disporre del BEGIN TRY…END CATCHcodice se si desidera RAISERROR()che influenzi il controllo del flusso.
binki

137

SQL 2012 introduce l'istruzione throw:

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

Se l'istruzione THROW viene specificata senza parametri, deve apparire all'interno di un blocco CATCH. Ciò causa la generazione dell'eccezione rilevata.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH

2
Attenzione, sembra che questa soluzione funzioni solo da sql server 2012 e versioni successive: msdn.microsoft.com/en-us/library/ee677615.aspx
Adi

3
@BogdanBogdanov In modo che tu possa registrare l'errore, possibilmente gestire alcune situazioni, ma se non puoi, allora vuoi rilanciare l'errore in modo che qualsiasi tentativo / cattura più in alto possa avere la possibilità di gestirlo
Robert McKee

Sì, @Robert McKee. Lo capisco. Spiacenti, ho dimenticato di cancellare questo commento.
Bogdan Bogdanov

4
Quel punto e virgola sulla ROLLBACKriga è importante! Senza di esso potresti ottenere un file SQLException: Cannot roll back THROW.
idontevenseethecode

5

Ripetizione all'interno del blocco CATCH (codice pre-SQL2012, utilizzare l'istruzione THROW per SQL2012 e versioni successive):

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

Penso che le tue scelte siano:

  • Non cogliere l'errore (lascia che bolle)
  • Creane uno personalizzato

Ad un certo punto, SQL probabilmente introdurrà un comando reraise o la capacità di rilevare solo determinati errori. Ma per ora, usa una soluzione alternativa. Scusate.


7
in sql 2012 puoi sollevare nuovamente un'eccezione utilizzando la nuova parola chiave THROW
sergiom

5
Sì. Ovviamente, non era disponibile quando è stata posta questa domanda.
Rob Farley,

Sarebbe più importante rilevare e lanciare un nuovo errore piuttosto che non prenderlo e lasciarlo "ribollire" perché probabilmente avrai bisogno di alcune attività di pulizia, azione correttiva e chiusura per gestire correttamente l'eccezione. Un esempio ovvio sarebbe chiudere e eliminare un cursore. Altri esempi possono essere l'esecuzione di una procedura di registrazione o il ripristino di alcuni dati.
Antony Booth

1

Non puoi: solo il motore può generare errori inferiori a 50000. Tutto quello che puoi fare è lanciare un'eccezione che assomiglia ...

Vedi la mia risposta qui per favore

L'interrogante qui ha utilizzato le transazioni lato client per fare ciò che voleva, il che penso sia un po 'sciocco ...


0

Ok, questa è una soluzione 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 noti il ​​blocco catch, non genera l'errore ma restituisce il numero di errore effettivo (e inoltre annullerà la transazione). Ora nel codice .NET, invece di catturare l'eccezione, se si utilizza ExecuteScalar (), si ottiene il numero di errore effettivo desiderato e si mostra il numero appropriato.

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

Spero che sia di aiuto,

EDIT: - Solo una nota, se si desidera ottenere il numero di record interessati e provare a utilizzare ExecuteNonQuery, la soluzione di cui sopra potrebbe non funzionare per te. Altrimenti, penso che sarebbe adatto a ciò di cui hai bisogno. Fammi sapere.


@ Ashish Gupta: grazie per aiuto, ma ho bisogno di un'eccezione da lanciare dal database al frontend, altrimenti ho molte opzioni aperte come print error_number (), return error_number e 1 u suggerito
Shantanu Gupta

0

Il modo per interrompere l'esecuzione in una stored procedure dopo che si è verificato un errore e riportare l'errore al programma chiamante è seguire ogni istruzione che potrebbe generare un errore con questo codice:

If @@ERROR > 0
Return

Sono rimasto sorpreso di scoprire che l'esecuzione in una stored procedure può continuare dopo un errore: non rendermene conto può portare ad alcuni bug difficili da rintracciare.

Questo tipo di gestione degli errori è parallela (pre .Net) Visual Basic 6. In attesa del comando Throw in SQL Server 2012.


0

Dato che non sei ancora passato al 2012, un modo per implementare il ribollire del codice di errore originale è utilizzare la parte del messaggio di testo dell'eccezione che stai (ri) lanciando dal blocco catch. Ricorda che può contenere una struttura, ad esempio, testo XML per il codice del chiamante da analizzare nel suo blocco catch.


0

È inoltre possibile creare una stored procedure wrapper per quegli scenari quando si desidera che l'istruzione SQL venga eseguita all'interno della transazione e fornire l'errore al codice.

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

Da un punto di vista del design, qual è lo scopo di lanciare eccezioni con numeri di errore originali e messaggi personalizzati? In una certa misura interrompe il contratto di interfaccia tra le applicazioni e il database. Se vuoi rilevare gli errori originali e gestirli con un codice più alto, non gestirli nel database. Quindi, quando si rileva un'eccezione, è possibile modificare il messaggio presentato all'utente in qualsiasi cosa desideri. Non lo farei però, perché rende il codice del database hmm "non corretto". Come altri hanno detto, dovresti definire una serie di tuoi codici di errore (sopra 50000) e lanciarli invece. Quindi puoi gestire i problemi di integrità ("Non sono consentiti valori duplicati") separatamente dai potenziali problemi aziendali: "Codice postale non valido", "Nessuna riga trovata corrispondente ai criteri" e così via.


9
Qual è lo scopo di generare eccezioni con numeri di errore originali e messaggi personalizzati? Supponiamo di voler gestire uno o due errori specifici (previsti) direttamente nel blocco catch e lasciare il resto per i livelli superiori. Quindi devi essere in grado di rilanciare le eccezioni che non hai gestito ... preferibilmente senza dover ricorrere alla segnalazione e alla gestione degli errori in un altro modo speciale.
Jenda

1
Oltre a ciò che ha spiegato @Jenda, mi piace usare try-catch per assicurarmi che l'esecuzione del codice non continui dopo un'eccezione, proprio come in C #:, try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }dove throw;solleverà solo l'eccezione originale, con il suo contesto originale.
R. Schreurs

Individuo gli errori e rilancio messaggi di errore personalizzati in SQL per aggiungere dettagli che descrivono la riga in cui si è verificato l'errore o altri dettagli (come i dati che tentano di inserire) per aiutarmi a rintracciare l'errore in un secondo momento.
Russell Hankins
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.