考虑一个接受 XML 参数并在同一架构中调用另一个存储过程的 CLR 存储过程:
[SqlFunction(DataAccess = DataAccessKind.Read,
IsDeterministic = false, IsPrecise = true,
SystemDataAccess = SystemDataAccessKind.Read)]
public static SqlBoolean TestTest(SqlXml Data)
{
using (var c = new SqlConnection("context connection=true"))
{
c.Open();
using (var cmd = new SqlCommand(@"select case when exists(select 0 from [testing].WillBeCalledByCLR(@d)) then 1 else 0 end;", c))
{
var p = cmd.Parameters.Add("@d", SqlDbType.Xml);
p.Direction = ParameterDirection.Input;
if (Data.IsNull)
{ p.Value = DBNull.Value; }
else
{ p.Value = Data; } // Or Data.Value
return (bool)cmd.ExecuteScalar();
}
}
}
此 CLR 函数在 SQL 中显示为:
create function [testing].[TestTest] ( @data xml )
returns bit
WITH CALLED ON NULL INPUT
AS
EXTERNAL NAME [Test].[Test.Test].[TestTest]
它调用的存储函数是这样的:
create function testing.WillBeCalledByCLR (@x xml = null)
returns table
as
return (
select 1 as one, 2 as two, 'three' as three
);
在此设置中,如果我这样调用 CLR 函数:
if testing.TestTest(null) = 1
begin
select 'Meaningful actions';
end;
或像这样:
declare @res bit = testing.TestTest(null);
或像这样:
declare @res bit;
set @res = testing.TestTest(null);
或像这样:
declare @res bit;
select @res = testing.TestTest(null);
然后我得到:
消息 2905,25 级,状态 1,第 6 行
Msg 0, Level 20, State 0, Line 0 当前命令发生严重错误。结果,如果有的话,应该丢弃。
但如果我这样称呼它:
select testing.TestTest(null);
或像这样:
declare @res bit;
set @res = (select testing.TestTest(null));
我得到了一个适当的值(例如1
)。
如果null
我传递了一个空字符串''
,而不是 ,则在所有情况下都成功调用了该函数。
为什么?我在 CLR 函数中做错了吗?
Microsoft SQL Server 2008 (SP2) - 10.0.4000.0 (X64)
2010 年 9 月 16 日 19:43:16
版权所有 (c) 1988-2008 Microsoft Corporation
Standard Edition (64-bit) on Windows NT 6.0 (Build 6002: Service Pack 2) (虚拟机)
总的来说,不,您的 .NET 代码没有做错任何事情。有一个小技术问题,但我们稍后会解决。
我能够在 SQL Server 2008 R2 RTM 中重现这个(错误和非错误场景)。起初,在“非错误”场景中,我遇到了另一个错误:
此错误是由于查询传回 an
INT
并且 C# 代码尝试将其转换为bool
. 我将查询更改为:这迫使要返回的值的数据类型是可以转换为
bool
. 这是我所指的“小错误”,我认为它是“小错误”,并不是真正的错误,因为我怀疑您的代码最初试图转换int
并返回 a ,SqlInt32
因为您声称传入一个空字符串允许它成功返回。如果您的代码试图强制转换为并返回,即使使用空字符串而不是NULL
输入值,这也不会成功返回。是的,我本可以将演员表更改为 be并将返回类型更改为 be ,但这不是结构上的更改。bool
SqlBoolean
(int)
SqlInt32
我测试了几种类型的更改:删除执行,删除
@d
参数并将其硬编码null
到查询中但仍然具有SqlParameter
定义,删除@d
参数和SqlParameter
等。最终归结为:仅存在一个SqlParameter
LOB 类型(即XML
,VARCHAR(MAX)
, )的 LOB 类型NVARCHAR(MAX)
,VARBINARY(MAX)
即使在查询中未使用,也会在传入NULL
. 但是,如果将这些MAX
类型中的任何一个更改为它们的最大非 MAX 长度(即分别为 8000、4000 和 8000),则在传递 a 时不会出错NULL
。因此,以下简化的代码应该足以在传入时重现错误NULL
到 SQLCLR 标量函数:现在,我无法使用 SQL Server 2012 SP3 或 SQL Server 2014 SP1 中的原始代码重现错误:-)。因此,我怀疑这是 CLR 2.0 和/或与 CLR 2.0 关联的 .NET Framework 版本(即“2.0”、“3.0”和“3.5”)的问题。或者,要考虑的另一个方面是,它与这个失败 (
declare @res bit; set @res = testing.TestTest(null);
) 而这个工作 (declare @res bit; set @res = (select testing.TestTest(null));
) 可能指向查询优化器的问题,而不是 CLR 版本的问题。或者也许是两者的结合。又或许是笛卡尔的讨厌鬼。无论哪种方式。(**请看文末评论)如果您能够升级到 SQL Server 2012 或更高版本,那么问题应该“神奇地”消失。但是,如果卡在 SQL Server 2008 / 2008 R2 上,如果输入为 ,我可以通过将参数声明为非 MAX 类型来解决该问题
NULL
,这应该没问题,因为不会有截断的风险当通过一个NULL
. 而且,在 T-SQL 对象的输入参数数据类型被调用的情况下XML
,它仍然可以在 .NET 代码中定义参数数据类型,NVARCHAR(4000)
因为它会在调用时将其隐式转换为XML
。在传递 a 时,以下代码在 SQL Server 2008 R2 中对我有用NULL
:** @MartinSmith留下了这条富有启发性的评论: