Kann die CLR-Ausführung fortgesetzt werden, nachdem in T-SQL 'THROW' festgestellt wurde?

7

Ich rufe eine gespeicherte T-SQL-Prozedur von einer gespeicherten CLR-Prozedur auf. und die THROWAnweisung in T-SQL stoppt die Ausführung von CLR, ohne in den catchBlock zu gehen. Gibt es etwas, das ich in meinem Code ändern kann, damit die CLR-Prozedur den catchBlock ausführt?

T-SQL:

CREATE TABLE t1 (ID INT PRIMARY KEY clustered);
CREATE TABLE t2 (ID INT CONSTRAINT fk_1 FOREIGN KEY REFERENCES t1(ID))
GO

CREATE PROC dbo.TestThrow 
AS
BEGIN
BEGIN TRY
    INSERT INTO t2 VALUES (1)
END TRY
BEGIN CATCH
    THROW 
END CATCH
END
GO

CLR:

    public static void TestThrow()
    {
        String query = "EXEC DBO.TESTTHROW";
        using (SqlConnection connection = new SqlConnection("context connection=true"))
        {
            connection.Open();
            try
            {
                using (SqlCommand command = new SqlCommand(query, connection))
                {

                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        SqlContext.Pipe.Send("no error");
                    }
                }
            }
            catch (SqlException e)
            {
                SqlContext.Pipe.Send("sqlexception");
                SqlContext.Pipe.Send(e.Message);
            }
            catch (Exception e)
            {
                SqlContext.Pipe.Send("exception");
                SqlContext.Pipe.Send(e.Message);
            }
        }
    }
msgisme
quelle

Antworten:

3

Es scheint, dass dieses Verhalten spezifisch für Verbindungen ist, die verwenden "Context Connection = true;". Ich habe versucht, dies zu umgehen, indem ich die try-catch-finallyStruktur geschrieben habe, anstatt das usingMakro zu verwenden, aber das hatte keine Auswirkung.

Vor fast drei Monaten wurde ein Microsoft Connect-Fehler in Bezug auf dieses Verhalten gemeldet. In diesem Connect-Fehler wurde spekuliert, dass THROWeine ThreadAbortException ausgelöst wird , die nicht über die Thread.ResetAbort- Methode zurückgesetzt werden kann. Ich habe versucht, diese Ausnahme explizit abzufangen und sogar anzurufen, Thread.ResetAbortwenn ich das Generikum abfange Exception, aber ohne Erfolg. Ich bin mir also nicht sicher, ob a ThreadAbortExceptionwirklich aufgerufen wird, aber unabhängig davon endet der aktuelle Prozess sofort. Außerdem wird der Fehler sogar als T-SQL-Fehler und nicht als ungerader .NET Framework-Fehler angezeigt.

Der Reporter dieses Connect-Fehlers wurde auf SQL Server 2014 und ich auf SQL Server 2012 getestet. Ich kann nicht mit Sicherheit sagen, ob dieses Verhalten in SQL Server 2016 noch vorhanden ist, aber ich vermute stark, dass dies der Fall ist, da dies anscheinend nicht der Fall ist Seien Sie (falls vorhanden) sehr bemüht, die CLR-Integration von SQL Server (dh SQLCLR) zu reparieren und / oder zu verbessern. Für den Moment und wahrscheinlich für die absehbare Zukunft gibt es drei mögliche Problemumgehungen, die für mich funktioniert haben:

  1. Ersetzen Sie THROWdurch RAISERROR(); RETURN;for procs, die möglicherweise von SQLCLR-Objekten aufgerufen werden. Die einzigen Nachteile, die mir einfallen, sind:
    • Benutzerdefiniert kann nicht im ERROR_NUMBERlaufenden Betrieb festgelegt werden
    • Kann nicht erneut geworfen werden, um das ursprüngliche, systemdefinierte ERROR_NUMBERan den Anrufer zu senden
  2. Wickeln Sie Ihre Abfrage in ein T-SQL TRY/ CATCH, das Folgendes verwendet RAISERROR:

      String _Query = @"
    BEGIN TRY
      EXEC dbo.TestThrowSql @CauseError;
    END TRY
    BEGIN CATCH
      DECLARE @ErrorMessage NVARCHAR(4000);
      SET @ErrorMessage = ERROR_MESSAGE();
      RAISERROR(@ErrorMessage, 16, 1);
    END CATCH;
    ";

    Der Vorteil hierbei ist, dass Sie die Verwendung fortsetzen können THROWund sie wie erwartet funktioniert, wenn sie von Nicht-SQLCLR-App-Code, von SQLCLR-App-Code, der die Kontextverbindung nicht verwendet, von anderen gespeicherten Prozeduren, in SQL Agent-Jobs usw. aufgerufen wird und Sie müssen nicht zurückgehen und vorhandene gespeicherte Prozeduren bearbeiten :-).

  3. Wechseln Sie zu einer regulären / externen Verbindungszeichenfolge. Die Nachteile hier sind:
    • Die Kontextverbindung ist viel schneller.
    • Die Kontextverbindung kann in einer SAFEAssembly erfolgen.
    • Die Kontextverbindung hat Zugriff auf sitzungsbasierte Elemente (dh CONTEXT_INFOlokale temporäre Tabellen usw.).
Solomon Rutzky
quelle