2015-04-07 53 views
20

我的同事在.NET 4.0中使用LINQ to SQL進行更復雜的查詢時出錯,但似乎在更簡單的情況下很容易重現。考慮一個名爲TransferJob的表,其中包含一個合成ID和一個位域。使用匿名對象和常量列的LINQ to SQL中的奇怪行爲

如果我們做下面的查詢

using (var ctx = DBDataContext.Create()) 
{ 
    var withOutConstant = ctx.TransferJobs.Select(x => new { Id = x.TransferJobID, IsAuto = x.IsFromAutoRebalance }); 
    var withConstant = ctx.TransferJobs.Select(x => new { Id = x.TransferJobID, IsAuto = true });//note we're putting a constant value in this one 

    var typeA = withOutConstant.GetType(); 
    var typeB = withConstant.GetType(); 
    bool same = typeA == typeB; //this is true! 

    var together = withOutConstant.Concat(withConstant); 
    var realized = together.ToList();//invalid cast exception 
} 

無效轉換異常被拋出注意的地方。但奇怪的是,我們在調試器中查看時有類型相等。

簡單地改變倒數第二行從的IQueryable的移動使用LINQ到對象

var together = withOutConstant.ToList().Concat(withConstant.ToList()); 
var realized = together.ToList();//no problem here 

然後一切工作正常預期。

經過一些初步的挖掘之後,我發現它看起來像LINQ to SQL的程序員正在考慮性能,並且實際上並沒有在withConstant版本中顯式設置true的情況下生成的SQL拉動常量值。

最後,如果切換命令一切似乎工作:

var together = withConstant.Concat(withOutConstant); //no problem this way 

不過,我還是想知道,如果更好的細節究竟怎麼回事。我覺得奇怪的是,這些被認爲是相同的類型,但會導致無效的轉換異常。封面下實際發生了什麼?我怎麼能去證明自己?

堆棧跟蹤:

at System.Data.SqlClient.SqlBuffer.get_Boolean() 
    at Read_<>f__AnonymousType2`2(ObjectMaterializer`1) 
    at System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext() 
    at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) 
    at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) 
    at KBA.GenericTestRunner.Program.Main(String[] args) in c:\Users\nick\Source\Workspaces\KBA\Main\KBA\KBA.GenericTestRunner\Program.cs:line 59 
    at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) 
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() 
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) 
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) 
    at System.Threading.ThreadHelper.ThreadStart() 

生成的SQL如下:

SELECT [t2].[TransferJobID] AS [Id], [t2].[IsFromAutoRebalance] AS [IsAuto] 
FROM (
    SELECT [t0].[TransferJobID], [t0].[IsFromAutoRebalance] 
    FROM [dbo].[TransferJob] AS [t0] 
    UNION ALL 
    SELECT [t1].[TransferJobID], @p0 AS [value] 
    FROM [dbo].[TransferJob] AS [t1] 
    ) AS [t2] 
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [1] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209 

隨着順序顛倒(不崩潰)的SQL是:

SELECT [t2].[TransferJobID] AS [Id], [t2].[value] AS [IsAuto] 
FROM (
    SELECT [t0].[TransferJobID], @p0 AS [value] 
    FROM [dbo].[TransferJob] AS [t0] 
    UNION ALL 
    SELECT [t1].[TransferJobID], [t1].[IsFromAutoRebalance] 
    FROM [dbo].[TransferJob] AS [t1] 
    ) AS [t2] 
-- @p0: Input Int (Size = -1; Prec = 0; Scale = 0) [1] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209 

要我以前的評論,當做的時候不會拉動常數

withConstant.ToList() 

SELECT [t0].[TransferJobID] AS [Id] 
FROM [dbo].[TransferJob] AS [t0] 
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.34209 
+0

你可以發佈堆棧跟蹤嗎?這將給出更多關於問題可能出在哪裏的提示。 –

+0

你可以在'IQueryable '中發佈'T'實例的類定義/模型嗎?另外,當你說'合成身份證'時,你的意思是一個自動增加你爲PK? – evanmcdonnal

+0

添加堆棧跟蹤 – nlh3

回答

8

過程中together.ToList()構造枚舉,我們嘗試移動到下一個元素在推遲查詢,也就是現在解決

MoveNext將從數據庫結果創建一些對象。 將數據庫查詢轉換爲DataReader,並從DataReader中提取一行。 現在,get_Boolean以對象VerifyType的方式實現,如果無效則拋出異常。

什麼你缺少你的問題表明的是together的查詢SqlText(以及你ctx.TransferJobs_sqlText),所以我不得不做出一個合理的假設。

TRUE轉換爲1,FALSE轉換爲0.轉換爲位將任意非零值提升爲1。

的LINQ到SQL數據源將trasform的Selecttrue參數類似

([table].[column] = 1) 

,並在

NOT ([table].[column] = 1) 

一個false參數所以 - 當你的第一個過濾器不是基於在true布爾條件 - 上述代碼行是一個轉換異常可能會發揮作用,如果Linq提供程序獲取不是0的對象(或false布爾對應於),我的猜測是null。

- 註腳 -

中所描述的登錄LINQ查詢下的實際SQL(除了Log財產,當然)一個幫手

Debug.WriteLine(together.ToString()); 

(或GetQueryText(query)debugging support

UPDATE

見過的SQL後,工作的解決方法是簡單地位字段映射像下面INT,使用DbType物業

[global::System.Data.Linq.Mapping.ColumnAttribute 
(Storage="_IsFromAutoRebalance", DbType="INT NOT NULL")] 
      public bool IsFromAutoRebalance 
      { 
       get 
       { 
        return this._IsFromAutoRebalance; 
       } 

相關(舊)VS反饋link其中的bug用封閉的​​建議的解決方法

5

這是一個L2S錯誤。這從以下事實可以清楚看出:

  • 這是L2S內部代碼的崩潰。這不是一個受控/預期的例外。
  • 這應該只是工作。
  • 對查詢的隨機更改會使崩潰消失。

以隨機方式修改查詢,直到碰巧工作。您已經有了一個很好的解決方法。留下一個C#註釋來記錄此查詢依賴於解決L2S錯誤的解決方法。

這些年來我發現可能有十幾個L2S錯誤(當發出異常或複雜的查詢時)。該產品被放棄,所以最終我們都必須切換到EF。我正在閱讀EF提交日誌,他們也有查詢翻譯錯誤。

封面下實際發生了什麼?

我無法回答,沒有太多的調查。調試L2S源代碼是可能的,但這是很多工作。這個問題只是出於好奇的原因,因爲你已經有了解決這個bug的方法。

我該如何去證明自己?

證明這是一個錯誤?上面給出了一些原因。

它看起來像LINQ to SQL的程序員正在考慮性能,並且實際上並沒有生成的SQL在withConstant版本中顯式設置爲true時拉取常量值。

這似乎對我而言並不合理。如果這是真的,我希望所有拉動的對象的值爲true。如果您建議的話,即使該列甚至沒有從數據庫中提取出來,我也不會期望發生無效投射。我認爲這是一個查詢翻譯錯誤。

另一個解決方法的想法是:

IsAuto = x.IsFromAutoRebalance == x.IsFromAutoRebalance 

現在這不再是一個常數,而是將永遠是真正在運行。 SQL Server查詢優化器能夠將此代碼簡化爲1。希望L2S不再執行破壞的重寫。


更新:

從T-SQL代碼,您發佈的錯誤是顯而易見的。參數@p0是一個int,而不是bool。這會導致結果列被提升爲int according to the rules。在這兩種情況下都是int。顯然,在其中一種情況下,L2S試圖將它作爲一個布爾值,另一個作爲一個int。抓取它作爲布爾不起作用和崩潰。因此,另一個解決方法是將查詢轉換爲使用整數(例如x.IsFromAutoRebalance ? 1 : 01)。