在.net中,当我使用SqlCommand
并调用使用RAISERROR
抛出的存储过程时,我可以读取该过程设置的输出变量。但是,从 T-SQL 批处理中调用该过程时,似乎根本没有设置OUTPUT
参数。
当过程调用RAISERROR
以指示错误时,如何从调用 T-SQL 中读取OUTPUT
参数@OUT_x
?
CREATE PROCEDURE dbo.p_x (
@OUT_x INT OUTPUT
) AS
BEGIN
SET @OUT_x = 1;
SELECT 'inner ', @OUT_x;
RAISERROR('yo', 11, 1);
END
GO
DECLARE @x INT = 0;
SELECT 'initial', @x;
BEGIN TRY
EXEC dbo.p_x @x OUTPUT;
END TRY
BEGIN CATCH
SELECT 'catch', @x;
END CATCH
SELECT 'end', @x;
输出:
------- -----------
initial 0
------ -----------
inner 1
----- -----------
catch 0
---- -----------
end 0
我通过在通过RAISERROR
抛出之前在存储过程中显示其值来证明@OUT_x设置为1
。我希望"catch"和"end"条目1
,但就好像参数值复制仅在存储过程成功完成时发生OUTPUT
。我知道当我通过SqlCommand
调用存储过程时,我可以读取这些OUTPUT
参数 - 为什么 T-SQL 批处理必须有所不同?
是否有不涉及更改存储过程的解决方法?我真的希望我的存储过程在特殊情况下抛出(RAISERROR
),但我还需要使用OUTPUT
参数来传达错误详细信息。
这是TRY .. CATCH
工作方式的限制:当发生RAISERROR
并输入CATCH
块时,它不会将输出参数的值复制回相应的局部变量。对此有话要说(结果可能不被依赖,因为毕竟发生了错误),但这同样可能是疏忽。对于初学者来说,不会尝试忽略存储过程在此之前生成的任何结果集(并将结果放入其中是一种可能的解决方法,但它需要更改存储过程)。据我所知,这种行为没有正式记录,因为T-SQL错误处理的许多事情都没有,但Erland Sommarskog的优秀文章提到了它。
由于结果的传输方式,客户端代码不会遇到相同的问题:分配输出参数时,将发回 TDS 数据包以指示;当发生错误时,将发回另一个 TDS 数据包以指示这一点。这些事情按时间顺序发生。客户端必须在出错时主动丢弃输出参数,而不仅仅是像TRY .. CATCH
那样忘记复制它们。
如果仅使用 输出参数来传达错误详细信息,请考虑不使用RAISERROR
而是使存储过程返回客户端处理的错误代码。当然,您冒着外部客户忘记检查这一点的风险,因为他们实际上不会得到SqlException
。另一种选择是在传递给RAISERROR
的消息中对所需的所有详细信息进行编码,或者如果它是小整数,则在ERROR_STATE()
中编码。这仍然需要修改存储过程;只要您打算使用TRY .. CATCH
,就没有办法解决这个问题。