Sql-Server-2008

將 NULL xml 值傳遞給某個 CLR 儲存函式會在以某種方式呼叫時崩潰

  • October 10, 2016

考慮一個接受 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) o​​n Windows NT 6.0 (Build 6002: Service Pack 2) (虛擬機)

總的來說,不,您的 .NET 程式碼沒有做錯任何事情。有一個小技術問題,但我們稍後會解決。

我能夠在 SQL Server 2008 R2 RTM 中重現這個(錯誤和非錯誤場景)。起初,在“非錯誤”場景中,我遇到了另一個錯誤:

指定的演員表無效

此錯誤是由於查詢傳回 anINT並且 C# 程式碼嘗試將其轉換為bool. 我將查詢更改為:

... then CONVERT(BIT, 1) else CONVERT(BIT, 0) end ...

這迫使要返回的值的數據類型是可以轉換為bool. 這是我所指的“小錯誤”,我認為它是“小錯誤”,並不是真正的錯誤,因為我懷疑您的程式碼最初試圖轉換int並返回 a ,SqlInt32因為您聲稱傳入一個空字元串允許它成功返回。如果您的程式碼試圖強制轉換為並返回,即使使用空字元串而不是NULL輸入值,這也不會成功返回。是的,我本可以將演員表更改為 be並將返回類型更改為 be ,但這不是結構上的更改。bool``SqlBoolean``(int)``SqlInt32

我測試了幾種類型的更改:刪除執行,刪除@d參數並將其硬編碼null到查詢中但仍然具有SqlParameter定義,刪除@d參數SqlParameter等。最終歸結為:僅存在一個SqlParameterLOB 類型(即XML, VARCHAR(MAX), )的 LOB 類型NVARCHAR(MAX)VARBINARY(MAX)即使在查詢中未使用,也會在傳入NULL. 但是,如果將這些MAX類型中的任何一個更改為它們的最大非 MAX 長度(即分別為 8000、4000 和 8000),則在傳遞 a 時不會出錯NULL。因此,以下簡化的程式碼應該足以在傳入時重現錯誤NULL到 SQLCLR 標量函式:

using (var cmd = new SqlCommand(@"SELECT 1;", c))
{
   var p = cmd.Parameters.Add("@d", SqlDbType.NVarChar, (int)SqlMetaData.Max);

   cmd.ExecuteScalar();
}

現在,我無法使用 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

SqlParameter _XmlData;

if (Data.IsNull)
{
   // Parameter can't be a LOB type if input is NULL
   _XmlData = new SqlParameter("@d", SqlDbType.NVarChar, 4000);
   _XmlData.Value = DBNull.Value;
}
else
{
   _XmlData = new SqlParameter("@d", SqlDbType.Xml);
   _XmlData.Value = Data.Value;
}

cmd.Parameters.Add(_XmlData);

****** @MartinSmith留下了這條富有啟發性的評論:

具體來說,錯誤似乎是“訪問衝突讀取位置 0x0000000000000000”,sqlservr.exe!CClrXvarProxy::EnsureBufferInit所以看起來像一個空指針錯誤,但Google搜尋這些方法名稱不會返回任何 KB 文章。

引用自:https://dba.stackexchange.com/questions/47091