THROW ohne Namen der aufrufenden Prozedur, sofern keine benutzerdefinierte Fehlermeldung angegeben ist

7

Ich habe ein Verhalten THROW, das ich nicht verstehen kann. Betrachten Sie die folgende gespeicherte Prozedur:

CREATE PROCEDURE usp_division_err AS 
SET NOCOUNT ON;
BEGIN TRY
    EXEC('select 1/0')
END TRY
BEGIN CATCH
    THROW;
END CATCH 

Wenn die Prozedur ausgeführt wird, wird der folgende Fehler ausgelöst:

Meldung 8134, Ebene 16, Status 1, Zeile 1
Fehler beim Teilen durch Null aufgetreten.

Beachten Sie, dass keine Informationen darüber enthalten sind, in welcher Prozedur der Fehler ausgelöst wurde. Das liegt daran, dass das fehlerhafte, dynamische SQL in einem anderen Bereich ausgeführt wird, und das ist in Ordnung. Ändern Sie den CATCH-block jedoch so, dass er so aussieht

BEGIN CATCH
    THROW 50000, 'An error occurred.', 1;
END CATCH 

und die Ausführung der Prozedur löst stattdessen diesen Fehler aus:

Meldung 50000, Ebene 16, Status 1, Prozedur usp_division_err, Zeile 7 [Stapelstartzeile 0]
Ein Fehler ist aufgetreten.

Der Fehler tritt immer noch beim Ausführen des dynamischen SQL auf, aber wenn ich die Fehlernummer und die Fehlermeldung (der erste und zweite Parameter von THROW) manuell spezifiziere , wird der Prozedurname der ausführenden Prozedur irgendwie angezeigt.

Warum erscheint der Prozedurname in der zweiten Fehlermeldung, aber nicht in der ersten?

krystah
quelle
Ich weiß nicht, ob dies hilft, aber ändern Sie einfach 1/0 anstelle von Exec ('select 1/0') und Sie erhalten beide: Nachricht 8134, Ebene 16, Status 1, Prozedur usp_division_err, Zeile 4 [Stapelstart Zeile 10] Division durch Null Fehler aufgetreten.
Kevin3NF
2
Nun, die einzige Erklärung ist, dass Sie, wenn Sie den Wurf ohne irgendetwas verwenden, die ursprüngliche Ausnahme umleiten (die Sie aufgrund des dynamischen SQL korrekt erklärt haben und keine Details anzeigen). Wenn Sie den Wurf mit benutzerdefiniertem Fehler verwenden, wird ein NEUER Fehler ausgegeben, daher werden Ihnen jetzt die Details angezeigt.
Renato Afonso
Ich habe festgestellt, dass viele EF-Setups den SP-Namen auch nicht zurückgeben, wenn der Fehler an einen Stack-Trace weitergegeben wird (nicht sicher, was die Ursache ist ..), daher verwende ich ihn immer FORMATMESSAGEin meinem äußersten Wurf, um den SP-Namen anzuhängen die Nachricht mit OBJECT_NAME(@@PROCID).
LowlyDBA

Antworten:

2

Code CATCHwie folgt:

BEGIN CATCH
    DECLARE @msg nvarchar(4000) = ERROR_MESSAGE()
    DECLARE @errno int = 50000 + ERROR_NUMBER();
    DECLARE @state int = ERROR_STATE();
    THROW @errno, @msg, @state;
END CATCH 

Auf diese Weise können Sie alle Parameter an die THROWAnweisung übergeben, um sie an den Aufrufer zurückzusenden.

Ich habe das so getestet:

USE tempdb;
IF OBJECT_ID('dbo.usp_division_err', 'P') IS NOT NULL 
DROP PROCEDURE dbo.usp_division_err;
GO
CREATE PROCEDURE dbo.usp_division_err AS 
SET NOCOUNT ON;
BEGIN TRY
    EXEC('select 1/0');
END TRY
BEGIN CATCH
    DECLARE @msg nvarchar(4000) = ERROR_MESSAGE()
    DECLARE @errno int = 50000 + ERROR_NUMBER();
    DECLARE @state int = ERROR_STATE();
    THROW @errno, @msg, @state;
END CATCH 
GO
EXEC dbo.usp_division_err;

Die Ergebnisse:

Meldung 58134, Ebene 16, Status 1, Prozedur dbo.usp_division_err, Zeile 10 [Stapelstartzeile 16]
Durch Null aufgetretener Fehler aufgetreten.

Um die native Fehlernummer zu erhalten, müssen Sie nur 50000 von der gemeldeten Fehlernummer abziehen. Da die Parameter für THROWoptional sind, müssen zwei Codepfade enthalten sein THROW, einer, der den Prozedurnamen angibt, und einer, der dies nicht tut. Ich überlasse es dem Leser zu entscheiden, welcher Codepfad das tut.

Max Vernon
quelle
1
Am Ende müssen wir also die THROW-Parameter manuell angeben, um den Kontext (die Prozedur) zu erhalten und gleichzeitig die innerste Fehlermeldung, Nummer und den Status der relevanten Funktionen beizubehalten. Raffiniert!
Krystah