Considere um procedimento armazenado CLR que aceita um parâmetro XML e chama outro procedimento armazenado no mesmo esquema:
[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();
}
}
}
Esta função CLR é visível no SQL como:
create function [testing].[TestTest] ( @data xml )
returns bit
WITH CALLED ON NULL INPUT
AS
EXTERNAL NAME [Test].[Test.Test].[TestTest]
A função armazenada que ele chama é esta:
create function testing.WillBeCalledByCLR (@x xml = null)
returns table
as
return (
select 1 as one, 2 as two, 'three' as three
);
Nesta configuração, se eu chamar a função CLR assim:
if testing.TestTest(null) = 1
begin
select 'Meaningful actions';
end;
ou assim:
declare @res bit = testing.TestTest(null);
ou assim:
declare @res bit;
set @res = testing.TestTest(null);
ou assim:
declare @res bit;
select @res = testing.TestTest(null);
então eu recebo:
Msg 2905, Nível 25, Estado 1, Linha 6
Msg 0, Level 20, State 0, Line 0 Ocorreu um erro grave no comando atual. Os resultados, se existirem, deveriam ser descartados.
Mas se eu chamar assim:
select testing.TestTest(null);
ou assim:
declare @res bit;
set @res = (select testing.TestTest(null));
Recebo um valor adequado de volta (por exemplo 1
).
Se, ao invés de null
, eu passar uma string vazia ''
, a função é chamada com sucesso em todos os casos.
Por quê? Fiz algo errado na minha função CLR?
Microsoft SQL Server 2008 (SP2) - 10.0.4000.0 (X64)
16 de setembro de 2010 19:43:16
Copyright (c) 1988-2008 Microsoft Corporation
Standard Edition (64 bits) no Windows NT 6.0 (Build 6002: Service Pack 2) (MV)
No geral, não, você não fez nada de errado com seu código .NET. Há um pequeno problema técnico, mas chegaremos a isso em um momento.
Consegui reproduzir isso (ambos os cenários de erro e não erro) no SQL Server 2008 R2 RTM. A princípio, no cenário "sem erro", recebi outro erro para:
Esse erro foi resultado da consulta passando de volta um
INT
e o código C# tentando convertê-lo em um arquivobool
. Alterei a consulta para:Isso força o tipo de dados do valor a ser retornado para ser algo que pode ser convertido em
bool
. Este foi o "erro menor" ao qual eu estava me referindo, e eu o considero "menor" e não realmente um erro, porque suspeito que seu código originalmente tentou converterint
e retornou umSqlInt32
, já que você afirma que passar uma string vazia permitiu retornar com sucesso. Não há como isso retornar com sucesso, mesmo com uma string vazia em vez doNULL
valor de entrada, se o seu código estiver tentando converterbool
e retornarSqlBoolean
. E sim, eu poderia ter mudado o cast para be(int)
e o tipo de retorno para beSqlInt32
, mas isso foi menos uma mudança estrutural.Testei vários tipos de alterações: remover a execução, remover o parâmetro
@d
e codificar onull
na consulta, mas ainda com oSqlParameter
definido, remover o@d
parâmetro e oSqlParameter
etc. de um tipo LOB (ou seja , , , , ), mesmo não utilizado na consulta, causava o "erro grave" ao passar um . No entanto, se qualquer um desses tipos for alterado para ter o comprimento máximo não MAX (ou seja, 8.000, 4.000 e 8.000, respectivamente), não haverá erro ao passar em um arquivo . Portanto, o seguinte código reduzido deve ser suficiente para reproduzir o erro ao passar em umSqlParameter
XML
VARCHAR(MAX)
NVARCHAR(MAX)
VARBINARY(MAX)
NULL
MAX
NULL
NULL
para a função escalar SQLCLR:Agora, não consegui reproduzir o erro com o código original no SQL Server 2012 SP3 ou SQL Server 2014 SP1 :-). Portanto, suspeito que esse seja um problema com o CLR 2.0 e/ou as versões do .NET Framework associadas ao CLR 2.0 (ou seja, "2.0", "3.0" e "3.5"). OU, outro aspecto a considerar é que com esta falha (
declare @res bit; set @res = testing.TestTest(null);
) enquanto isso funciona (declare @res bit; set @res = (select testing.TestTest(null));
) isso pode apontar para um problema com o Query Optimizer e não com a versão CLR. Ou talvez seja uma combinação dos dois. Ou talvez seja o incômodo Demônio Maligno de Descartes . De qualquer jeito. ( ** por favor, veja o comentário no final )Se você conseguir mudar para o SQL Server 2012 ou mais recente, o problema deve desaparecer "magicamente". MAS, se travado no SQL Server 2008/2008 R2, consegui contornar o problema declarando o parâmetro como um tipo não MAX se a entrada for
NULL
, o que deve ser bom, pois não haverá risco de truncamento ao passar por umNULL
. E, no caso do tipo de dados do parâmetro de entrada do objeto T-SQL sendo chamado sendoXML
, ele ainda funciona para definir o tipo de dados do parâmetro no código .NET,NVARCHAR(4000)
pois ele o converterá implicitamenteXML
quando chamado. O código a seguir funcionou para mim no SQL Server 2008 R2 ao passar em umNULL
:** @MartinSmith deixou este comentário esclarecedor: