將 NULL xml 值傳遞給某個 CLR 儲存函式會在以某種方式呼叫時崩潰
考慮一個接受 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
. 我將查詢更改為:... 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
等。最終歸結為:僅存在一個SqlParameter
LOB 類型(即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 文章。