Question

I am calling a T-SQL stored procedure from a CLR stored procedure. and the THROW statement in T-SQL stops the execution of CLR without going into the catch block. Is there something I can change in my code to cause the CLR procedure to execution the catch block?

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);
            }
        }
    }
Was it helpful?

Solution

It appears that this behavior is specific to connections using "Context Connection = true;". I have tried to get around this by writing out the try-catch-finally structure instead of using the using macro, but that had no effect.

A Microsoft Connect bug was filed almost 3 months ago regarding this behavior. In that Connect bug it was speculated that THROW raises a ThreadAbortException that cannot be reset via the Thread.ResetAbort method. I tried explicitly catching this exception, and even calling Thread.ResetAbort when catching the generic Exception, but to no avail. So I am not sure if a ThreadAbortException is really being called, but regardless, the current process ends immediately. And, it even displays the error as a T-SQL error and not a .NET Framework error, which is odd.

The reporter of that Connect bug tested on SQL Server 2014, and I tested on SQL Server 2012. I cannot say for certain if this behavior still exists in SQL Server 2016, but I strongly suspect that it does given that there doesn't seem to be much effort (if any) put into fixing and/or improving SQL Server's CLR Integration (i.e. SQLCLR). So for now, and likely the foreseeable future, there are three possible work-arounds that have worked for me:

  1. Replace THROW with RAISERROR(); RETURN; for procs that might be called by SQLCLR objects. The only downsides I can think of are:
    • Can't set a custom ERROR_NUMBER on-the-fly
    • Can't re-throw to send the original, system-defined ERROR_NUMBER to the caller
  2. Wrap your query in a T-SQL TRY / CATCH that uses 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;
    ";
    

    The benefit here is that you can continue to use THROW and it will work as expected when called by non-SQLCLR app code, by SQLCLR app code that is not using the Context Connection, by other Stored Procedures, in SQL Agent jobs, etc. That and you don't have to go back and edit any existing Stored Procedures :-).

  3. Switch to using a regular / external connection string. The downsides here are:
    • The Context Connection is much faster.
    • The Context Connection can be done in a SAFE Assembly.
    • The Context Connection has access to session-based items (i.e. CONTEXT_INFO, local temporary tables, etc.)

OTHER TIPS

SET XACT_ABORT ON makes THROW behavior same as RAISERROR. See here - https://stackoverflow.com/questions/17781196/throw-t-sql-exception-from-clr

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top