在 T-SQL 中遇到“THROW”后,CLR 能否继续执行?

msg*_*sme 7 sql-server c# sql-clr

我正在从 CLR 存储过程调用 T-SQL 存储过程。并且THROWT-SQL 中的语句在不进入catch块的情况下停止 CLR 的执行。是否可以在我的代码中更改某些内容以导致 CLR 过程执行该catch块?

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
Run Code Online (Sandbox Code Playgroud)

克莱尔:

    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);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Sol*_*zky 3

看来此行为特定于使用 的连接"Context Connection = true;"。我试图通过写出try-catch-finally结构而不是使用using宏来解决这个问题,但这没有效果。

大约 3 个月前,已针对此行为提交了Microsoft Connect 错误。在该 Connect bug 中,推测会THROW引发无法通过Thread.ResetAbort方法重置的ThreadAbortException。我尝试显式捕获此异常,甚至在捕获 generic 时调用,但无济于事。所以我不确定 a 是否真的被调用,但无论如何,当前进程立即结束。而且,它甚至将错误显示为 T-SQL 错误而不是 .NET Framework 错误,这很奇怪。Thread.ResetAbortExceptionThreadAbortException

该 Connect bug 的记者在 SQL Server 2014 上进行了测试,我也在 SQL Server 2012 上进行了测试。我不能肯定地说这种行为在 SQL Server 2016 中是否仍然存在,但我强烈怀疑它确实存在,因为似乎没有投入大量精力(如果有的话)来修复和/或改进 SQL Server 的 CLR 集成(即 SQLCLR)。因此,就目前而言,可能在可预见的未来,三种可能的解决方法对我有用:

  1. 替换THROWRAISERROR(); RETURN;可能由 SQLCLR 对象调用的过程。我能想到的唯一缺点是:
    • 无法ERROR_NUMBER即时设置自定义
    • 无法重新抛出以将系统定义的原始数据发送ERROR_NUMBER给调用者
  2. 将您的查询包装在 T-SQL TRY/中CATCH,使用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;
    ";
    
    Run Code Online (Sandbox Code Playgroud)

    这样做的好处是,您可以继续使用THROW,并且当被非 SQLCLR 应用程序代码、不使用上下文连接的 SQLCLR 应用程序代码、其他存储过程、SQL 代理作业等调用时,它将按预期工作。并且您不必返回并编辑任何现有的存储过程:-)。

  3. 切换到使用常规/外部连接字符串。这里的缺点是:
    • 上下文连接要快得多。
    • 上下文连接可以在程序集中完成SAFE
    • 上下文连接可以访问基于会话的项目(即CONTEXT_INFO本地临时表等)