Consider a CLR stored procedure that accepts an XML parameter and calls another stored procedure in the same schema:
[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();
}
}
}
This CLR function is visible in SQL as:
create function [testing].[TestTest] ( @data xml )
returns bit
WITH CALLED ON NULL INPUT
AS
EXTERNAL NAME [Test].[Test.Test].[TestTest]
The stored function it calls is this:
create function testing.WillBeCalledByCLR (@x xml = null)
returns table
as
return (
select 1 as one, 2 as two, 'three' as three
);
In this setup, if I call the CLR function like this:
if testing.TestTest(null) = 1
begin
select 'Meaningful actions';
end;
or like this:
declare @res bit = testing.TestTest(null);
or like this:
declare @res bit;
set @res = testing.TestTest(null);
or like this:
declare @res bit;
select @res = testing.TestTest(null);
then I get:
Msg 2905, Level 25, State 1, Line 6
Msg 0, Level 20, State 0, Line 0
A severe error occurred on the current command. The results, if any, should be discarded.
But if I call it like this:
select testing.TestTest(null);
or like this:
declare @res bit;
set @res = (select testing.TestTest(null));
I get a proper value back (e.g. 1
).
If, instead of null
, I pass an empty string ''
, the function is successfully called in all cases.
Why? Did I do something wrong in my CLR function?
Microsoft SQL Server 2008 (SP2) – 10.0.4000.0 (X64)
Sep 16 2010 19:43:16
Copyright (c) 1988-2008 Microsoft Corporation
Standard Edition (64-bit) on Windows NT 6.0 (Build 6002: Service Pack 2) (VM)
Best Answer
Overall, no, you did not do anything wrong with your .NET code. There is a minor technical issue, but we will get to that in a moment.
I was able to reproduce this (both error and non-error scenarios) in SQL Server 2008 R2 RTM. At first, in the "non-error" scenario, I was getting another error for:
This error was a result of the query passing back an
INT
and the C# code attempting to cast it to abool
. I changed the query to be:That forces the datatype of the value to be returned to be something that can be cast into
bool
. This was the "minor error" that I was referring to, and I consider it "minor" and not really an error because I suspect your code originally attempted to cast intoint
and returned aSqlInt32
since you claim that passing in an empty string allowed it to return successfully. There is no way this would ever return successfully, even with an empty string instead ofNULL
for the input value, if your code was attempting to cast tobool
and returnSqlBoolean
. And yes, I could have changed the cast to be(int)
and the return type to beSqlInt32
, but this was less of a structural change.I tested several types of changes: removing the execution, removing the
@d
parameter and hard-coding thenull
into the query but still having theSqlParameter
defined, removing the@d
parameter and theSqlParameter
, etc. What it finally came down to is this: merely the existence of aSqlParameter
of a LOB type (i.e.XML
,VARCHAR(MAX)
,NVARCHAR(MAX)
,VARBINARY(MAX)
), even if unused in the query, caused the "severe error" when passing in aNULL
. However, if any of thoseMAX
types were changed to be their maximum non-MAX lengths (i.e. 8000, 4000, and 8000 respectively), then there was no error when passing in aNULL
. Hence the following reduced code should be enough to reproduce the error when passing in aNULL
to the SQLCLR scalar function:Now, I was not able to reproduce the error with the original code in SQL Server 2012 SP3 or SQL Server 2014 SP1 :-). Therefore, I suspect that this was an issue with CLR 2.0 and/or the .NET Framework versions associated with CLR 2.0 (i.e. "2.0", "3.0", and "3.5"). OR, another aspect to consider is that it with this failing (
declare @res bit; set @res = testing.TestTest(null);
) while this works (declare @res bit; set @res = (select testing.TestTest(null));
) that could point to a problem with the Query Optimizer and not with the CLR version. Or maybe it is a combination of the two. Or perhaps it is Descartes's pesky Evil Demon. Either way. ( ** please see comment at the end )If you are able to move up to SQL Server 2012 or newer, then the problem should "magically" disappear. BUT, if stuck on SQL Server 2008 / 2008 R2, I was able to work around the issue by declaring the parameter to be a non-MAX type if the input is
NULL
, which should be fine since there won't be any risk of truncation when passing through aNULL
. And, in the case of the input parameter datatype of the T-SQL object being called beingXML
, it still works to define the parameter datatype in the .NET code asNVARCHAR(4000)
as it will implicitly convert that intoXML
when called. The following code worked for me in SQL Server 2008 R2 when passing in aNULL
:** @MartinSmith left this illuminating comment: