[C#] 如何編寫異步LINQ查詢?


Answers

SoftwareJedi和ulrikb的(也稱為user316318)解決方案適用於任何LINQ類型,但是(如Chris Moschini所指出的)不會委託使用Windows I / O完成端口的底層異步調用。

Wesley Bakker的異步DataContext文章(由Scott Hanselman的博客文章觸發)描述了LINQ to SQL的類,該類使用了使用Windows I / O完成端口的sqlCommand.BeginExecuteReader / sqlCommand.EndExecuteReader。

I / O完成端口為處理多處理器系統上的多個異步I / O請求提供了高效的線程模型。

Question

在我讀了一堆LINQ相關的東西之後,我突然意識到沒有文章介紹如何編寫異步LINQ查詢。

假設我們使用LINQ to SQL,下面的語句是明確的。 但是,如果SQL數據庫響應緩慢,則使用此代碼塊的線程將受到阻礙。

var result = from item in Products where item.Price > 3 select item.Name;
foreach (var name in result)
{
    Console.WriteLine(name);
}

似乎目前的LINQ查詢規範不提供支持。

有什麼辦法做異步編程的LINQ? 它的工作原理就好像有一個回調通知,當結果準備好使用時,在I / O上沒有任何阻塞延遲。




我開始了一個名為Asynq的簡單github項目來執行異步的LINQ到SQL查詢。 這個想法很簡單,儘管在這個階段“脆弱”(截至2011年8月16日):

  1. 讓LINQ-to-SQL通過DataContext.GetCommand()將你的IQueryable翻譯成一個DbCommand
  2. 對於從GetCommand()獲得的抽象DbCommand實例來建立SQL 200 [058]以獲得SqlCommand 。 如果你使用的是SQL CE,那麼你的運氣不好,因為SqlCeCommand不會公開BeginExecuteReaderEndExecuteReader的異步模式。
  3. 使用BeginExecuteReaderEndExecuteReader關閉SqlCommand使用標準的.NET框架異步I / O模式,在傳遞給BeginExecuteReader方法的完成回調委託中獲得一個DbDataReader
  4. 現在我們有了一個DbDataReader ,我們不知道它包含的是什麼列,也不知道如何將這些值映射回IQueryableElementType (最可能是連接的匿名類型)。 當然,在這一點上,您可以手寫您自己的列映射器,將其結果轉化為匿名類型或其他內容。 您必須為每個查詢結果類型編寫一個新的查詢,具體取決於LINQ-to-SQL如何處理您的IQueryable以及它生成的SQL代碼。 這是一個非常討厭的選項,我不推薦它,因為它不可維護,也不總是正確的。 LINQ to SQL可以根據傳入的參數值更改查詢形式,例如query.Take(10).Skip(0)產生與query.Take(10).Skip(10)不同的SQL一個不同的結果集模式。 你最好的選擇是以編程方式處理這個實現問題:
  5. “重新實現”一個簡單的運行時對象實現器,根據IQueryableElementType Type的LINQ-to-SQL映射屬性,按照定義的順序將列從DbDataReader中提取出來。 正確實施這可能是此解決方案中最具挑戰性的部分。

正如其他人所發現的, DataContext.Translate()方法不處理匿名類型,只能將DbDataReader直接映射到正確屬性的LINQ-to-SQL代理對象。 由於大多數值得在LINQ中編寫的查詢將涉及復雜的連接,這最終需要匿名類型作為最終的select子句,所以無論如何都使用這個提供的泛化DataContext.Translate()方法是毫無意義的。

利用現有的成熟的LINQ-to-SQL IQueryable提供程序時,此解決方案有一些小缺陷:

  1. 你不能將一個對象實例映射到IQueryable的最後一個select子句中的多個匿名類型屬性,例如from x in db.Table1 select new { a = x, b = x } 。 LINQ-to-SQL在內部跟踪哪些列標題映射到哪些屬性; 它不會將此信息公開給最終用戶,因此您不知道DbDataReader中的哪些列被重用,哪些是“不同的”。
  2. 你不能在最後的select子句中包含常量值 - 這些值不會被轉換成SQL,而會從DbDataReader缺失,所以你必須建立自定義的邏輯從IQueryableExpression樹中取出這些常量值,相當麻煩,根本不合理。

我確定還有其他的查詢模式可能會被破壞,但是這些是我能想到的兩個最大的問題,可能會導致現有的LINQ到SQL數據訪問層出現問題。

這些問題很容易失敗 - 只是不要在你的查詢中做這些事情,因為這兩個模式都不能為查詢的最終結果提供任何好處。 希望這個建議適用於所有可能導致對象實現問題的查詢模式:-P。 解決無法訪問LINQ-to-SQL的列映射信息是一個很難的問題。

解決問題的一個更“完整”的方法是有效地重新實現幾乎所有的LINQ到SQL,這是更加耗時的:-P。 從一個高質量的開源LINQ到SQL提供者的實現將是一個很好的方法來到這裡。 您需要重新實現的原因是,您可以訪問所有用於將DbDataReader結果物化為對象實例的列映射信息,而不會丟失任何信息。