wie man dieselbe Ausnahme in SQL Server erneut auslöst

85

Ich möchte dieselbe Ausnahme in SQL Server erneut auslösen, die in meinem Try-Block aufgetreten ist. Ich kann dieselbe Nachricht senden, aber ich möchte denselben Fehler auslösen.

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

Diese Zeile zeigt Fehler, aber ich möchte Funktionalität so etwas. Dies löst einen Fehler mit der Fehlernummer 50000 aus, aber ich möchte, dass eine Fehlernummer ausgegeben wird, die ich übergebe @@error.

Ich möchte diesen Fehler nicht im Frontend erfassen

dh

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

Ich möchte diese Funktionalität. was mit Raiseerror nicht erreicht werden kann. Ich möchte keine benutzerdefinierte Fehlermeldung am Backend geben.

RAISEERROR sollte den unten genannten Fehler zurückgeben, wenn ich ErrorNo übergebe, um in catch geworfen zu werden

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

Zeile 14 Verletzung der UNIQUE KEY-Einschränkung 'UK_DomainCode'. Es kann kein doppelter Schlüssel in das Objekt 'Tags.tblDomain' eingefügt werden. Die Anweisung wurde beendet.

BEARBEITEN:

Was kann der Nachteil sein, wenn der try catch-Block nicht verwendet wird, wenn eine Ausnahme im Frontend behandelt werden soll, wenn die gespeicherte Prozedur mehrere Abfragen enthält, die ausgeführt werden müssen

Shantanu Gupta
quelle

Antworten:

118

Hier ist ein voll funktionsfähiges Beispiel für einen sauberen Code, mit dem eine Reihe von Anweisungen zurückgesetzt werden können, wenn ein Fehler auftritt, und die Fehlermeldung gemeldet wird.

begin try
    begin transaction;

    ...

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

Vor 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
quelle
8
Ich habe dies mitten in einer gespeicherten Prozedur verwendet und festgestellt, dass sie danach weiter ausgeführt wird raiserror , was sich davon unterscheidet, wie c # nach a beendet wird throw. Also habe ich ein returnInside hinzugefügt, catchweil ich diesem Verhalten entsprechen wollte.
Brian J
@ BogdanBogdanov Ich habe Ihre Bearbeitung zurückgesetzt, weil der Punkt dieses Codes minimal sein soll und nicht den tatsächlichen Code beeinträchtigen soll, der anstelle des ...
Ben Gripka
Ok, kein Problem, @Ben Gripka. Ich versuche es auf dem Bildschirm besser lesbar zu machen. Vielen Dank, dass Sie den Grund für das Rollback angegeben haben.
Bogdan Bogdanov
1
@BrianJ: Normalerweise hängt es von der Schwere des ursprünglichen Fehlers ab, ob die Ausführung gestoppt wird oder nicht. Wenn der Schweregrad> = 11 ist, sollte die Ausführung gestoppt werden. Es ist wirklich komisch, weil der Raiserror im Catch-Block mit einem Schweregrad> = 11 die Ausführung nicht mehr stoppt. Ihre Beobachtung ist sehr gut und zeigt, wie hirntot SQL Server ist, mindestens 2008r2. Die neueren Versionen scheinen besser zu sein.
Costa
1
@costa Siehe RAISERROR()die Dokumente . Der Schweregrad ≥11 springt nur zu einem CATCHBlock, wenn er sich innerhalb eines TRYBlocks befindet. Also Sie müssen also BEGIN TRY…END CATCHCode haben, wenn Sie RAISERROR()die Flusskontrolle beeinflussen möchten .
Binki
135

SQL 2012 führt die throw-Anweisung ein:

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

Wenn die THROW-Anweisung ohne Parameter angegeben wird, muss sie in einem CATCH-Block erscheinen. Dadurch wird die abgefangene Ausnahme ausgelöst.

BEGIN TRY
    BEGIN TRANSACTION
    ...
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION;
    THROW
END CATCH
Michael
quelle
2
Achtung, es sieht so aus, als ob diese Lösung nur von SQL Server 2012 und höher funktioniert
Adi
3
@BogdanBogdanov Damit Sie den Fehler protokollieren können, können Sie möglicherweise einige Situationen behandeln. Wenn Sie dies jedoch nicht können, möchten Sie den Fehler erneut auslösen, damit jeder höhere Versuch / Fang eine Chance hat, ihn zu behandeln
Robert McKee
Ja, @ Robert McKee. Ich finde das heraus. Entschuldigung, das hat vergessen, diesen Kommentar zu löschen.
Bogdan Bogdanov
3
Das Semikolon in der ROLLBACKZeile ist wichtig! Ohne es können Sie eine bekommen SQLException: Cannot roll back THROW.
idontevenseethecode
5

Erneutes Werfen innerhalb des CATCH-Blocks (Code vor SQL2012, verwenden Sie die Anweisung THROW für SQL2012 und höher):

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
quelle
4

Ich denke, Ihre Entscheidungen sind:

  • Den Fehler nicht abfangen (sprudeln lassen)
  • Erhöhen Sie eine benutzerdefinierte

Irgendwann wird SQL wahrscheinlich einen Reraise-Befehl einführen oder nur bestimmte Fehler abfangen. Verwenden Sie vorerst eine Problemumgehung. Es tut uns leid.

Rob Farley
quelle
7
In SQL 2012 können Sie eine Ausnahme mit dem neuen Schlüsselwort
THROW
5
Ja. Das war natürlich nicht verfügbar, als diese Frage gestellt wurde.
Rob Farley
Es wäre wichtiger, einen neuen Fehler abzufangen und auszulösen, als ihn nicht abzufangen und in die Luft zu sprudeln, da Sie wahrscheinlich einige Bereinigungs-, Korrektur- und Schließungsaktivitäten benötigen, um die Ausnahme ordnungsgemäß zu behandeln. Ein naheliegendes Beispiel wäre das Schließen und Entsorgen eines Cursors. Andere Beispiele können sein, eine Protokollierungsprozedur auszuführen oder einige Daten zurückzusetzen.
Antony Booth
1

Sie können nicht: Nur die Engine kann Fehler unter 50000 auslösen. Sie können lediglich eine Ausnahme auslösen, die so aussieht ...

Siehe meine Antwort hier bitte

Der Fragesteller hier hat clientseitige Transaktionen verwendet, um das zu tun, was er wollte, was ich für ein bisschen albern halte ...

gbn
quelle
0

Ok, das ist eine Problemumgehung ... :-)

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

Wenn Sie den catch-Block notieren, wird der Fehler nicht ausgelöst, sondern die tatsächliche Fehlernummer zurückgegeben (und die Transaktion wird ebenfalls zurückgesetzt). Wenn Sie jetzt in Ihrem .NET-Code die Ausnahme abfangen und ExecuteScalar () verwenden, erhalten Sie die gewünschte tatsächliche Fehlernummer und zeigen die entsprechende Nummer an.

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

Hoffe das hilft,

BEARBEITEN: - Nur ein Hinweis: Wenn Sie die Anzahl der betroffenen Datensätze ermitteln und versuchen möchten, ExecuteNonQuery zu verwenden, funktioniert die oben genannte Lösung möglicherweise nicht für Sie. Ansonsten denke ich, es würde passen, was Sie brauchen. Gib mir Bescheid.

Ashish Gupta
quelle
@ Ashish Gupta: Danke für die Hilfe, aber ich brauche eine Ausnahme, um von der Datenbank ins Frontend geworfen zu werden, sonst habe ich viele Optionen geöffnet, wie print error_number (), return error_number und die 1 u vorgeschlagen
Shantanu Gupta
0

Die Möglichkeit, die Ausführung in einer gespeicherten Prozedur zu stoppen, nachdem ein Fehler aufgetreten ist, und den Fehler an das aufrufende Programm zurückzusenden, besteht darin, jeder Anweisung zu folgen, die einen Fehler mit diesem Code auslösen könnte:

If @@ERROR > 0
Return

Ich war selbst überrascht, als ich herausfand, dass die Ausführung in einer gespeicherten Prozedur nach einem Fehler fortgesetzt werden kann. Wenn Sie dies nicht bemerken, kann dies zu schwer auffindbaren Fehlern führen.

Diese Art der Fehlerbehandlung Parallelen (vor .Net) Visual Basic 6. Wir freuen uns auf den Befehl Throw in SQL Server 2012.

Chuck Bevitt
quelle
0

Da Sie noch nicht zu 2012 gewechselt sind, besteht eine Möglichkeit, das Sprudeln des ursprünglichen Fehlercodes zu implementieren, darin, den Textnachrichtenteil der Ausnahme zu verwenden, die Sie aus dem catch-Block (erneut) auslösen. Denken Sie daran, dass es eine Struktur enthalten kann, z. B. XML-Text, den Ihr Aufrufercode in seinem catch-Block analysieren kann.

Yuri Makassiouk
quelle
0

Sie können auch eine Wrapper-gespeicherte Prozedur für diese Szenarien erstellen, wenn die SQL-Anweisung innerhalb der Transaktion ausgeführt werden soll, und den Fehler an Ihren Code weiterleiten.

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
quelle
-2

Was bringt es aus gestalterischer Sicht, Ausnahmen mit ursprünglichen Fehlernummern und benutzerdefinierten Meldungen auszulösen? Bis zu einem gewissen Grad bricht es den Schnittstellenvertrag zwischen Anwendungen und der Datenbank. Wenn Sie Originalfehler abfangen und in höherem Code behandeln möchten, behandeln Sie sie nicht in der Datenbank. Wenn Sie dann eine Ausnahme abfangen, können Sie die dem Benutzer präsentierte Nachricht in eine beliebige Nachricht ändern. Ich würde es aber nicht tun, weil es Ihren Datenbankcode hmm 'nicht richtig' macht. Wie andere sagten, sollten Sie einen Satz Ihrer eigenen Fehlercodes (über 50000) definieren und diese stattdessen auslösen. Anschließend können Sie Integritätsprobleme ("Doppelte Werte sind nicht zulässig") getrennt von potenziellen geschäftlichen Problemen behandeln - "Postleitzahl ist ungültig", "Es wurden keine Zeilen gefunden, die den Kriterien entsprechen" usw.

Piotr Rodak
quelle
9
Was bringt es, Ausnahmen mit ursprünglichen Fehlernummern und benutzerdefinierten Meldungen auszulösen? Angenommen, Sie möchten einen oder zwei bestimmte (erwartete) Fehler direkt im Catch-Block behandeln und den Rest für die höheren Ebenen belassen. Daher müssen Sie in der Lage sein, die Ausnahmen, die Sie nicht behandelt haben, erneut zu lösen ... vorzugsweise ohne auf eine andere, spezielle Weise auf die Meldung und Behandlung der Fehler zurückgreifen zu müssen.
Jenda
1
Zusätzlich zu dem, was @Jenda erklärt hat, verwende ich gerne try-catch, um sicherzustellen, dass die Codeausführung nach einer Ausnahme nicht fortgesetzt wird, ähnlich wie in C #:, try { code(); } catch (Exception exc) { log(exc); throw; } finally { cleanup(); }wo throw;nur die ursprüngliche Ausnahme mit ihrem ursprünglichen Kontext ausgelöst wird .
R. Schreurs
Ich fange Fehler ab und werfe benutzerdefinierte Fehlermeldungen in SQL erneut aus, um Details hinzuzufügen, die beschreiben, in welcher Zeile der Fehler aufgetreten ist, oder andere Details (z. B. die Daten, die eingefügt werden sollen), um den Fehler später aufzuspüren.
Russell Hankins