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



1 Answers

TheSoftwareJedi和ulrikb (又名user316318)解決方案適用於任何LINQ類型,但(正如Chris Moschini指出的那樣)不會委託利用Windows I / O完成端口的底層異步調用。

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

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-to-SQL查詢。 這個想法非常簡單,儘管在這個階段是“脆弱的”(截至2011年8月16日):

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

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

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

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

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

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

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




Related