2013-02-07 71 views
4

情況:我有List<IQueryable<MyDataStructure>>。我想在它們中的每一個上並行運行一個linq查詢,然後加入結果。在多個IQueryable上並行運行相同的linq查詢?

問題:如何創建一個可以作爲參數傳遞的linq查詢?

示例代碼:

這裏的一些簡化的代碼。首先,我有IQueryable<string>集合:

public List<IQueryable<string>> GetQueries() 
    { 
     var set1 = (new List<string> { "hello", "hey" }).AsQueryable(); 
     var set2 = (new List<string> { "cat", "dog", "house" }).AsQueryable(); 
     var set3 = (new List<string> { "cat", "dog", "house" }).AsQueryable(); 
     var set4 = (new List<string> { "hello", "hey" }).AsQueryable(); 

     var sets = new List<IQueryable<string>> { set1, set2, set3, set4 }; 

     return sets; 
    } 

我想找到所有與字母「H」開始的話。使用單一IQueryable<string>這是很容易:

query.Where(x => x.StartsWith("h")).ToList() 

但我想運行對所有並行IQueryable<string>對象相同的查詢,然後合併結果。這裏有一種方法:

 var result = new ConcurrentBag<string>(); 
     Parallel.ForEach(queries, query => 
     { 
      var partOfResult = query.Where(x => x.StartsWith("h")).ToList(); 

      foreach (var word in partOfResult) 
      { 
       result.Add(word); 
      } 
     }); 

     Console.WriteLine(result.Count); 

但我希望這是一個更通用的解決方案。所以我可以單獨定義linq操作並將它作爲參數傳遞給方法。這樣的事情:

 var query = Where(x => x.FirstName.StartsWith("d") && x.IsRemoved == false) 
      .Select(x => x.FirstName) 
      .OrderBy(x => x.FirstName); 

     var queries = GetQueries(); 

     var result = Run(queries, query); 

但我在如何做到這一點的損失。有任何想法嗎?

回答

5

所以第一件事情,你想要的是採取一系列查詢,執行所有這些查詢,然後得到結果列表的方式。這是很簡單的:

public static IEnumerable<T> Foo<T>(IEnumerable<IQueryable<T>> queries) 
{ 
    return queries.AsParallel() 
      .Select(query => query.ToList()) 
      .SelectMany(results => results); 
} 

因爲我們執行它(調用它的ToList)每個查詢和它並行完成,這要歸功於AsParallel,然後將結果通過SelectMany壓扁成一個單一的序列。

您希望做的另一件事是在一系列查詢中爲每個查詢添加一些查詢操作。這並不需要進行並行(由於延遲執行,來電來WhereOrderBy等採取幾乎沒有時間),正好可以通過Select完成:

var queries = GetQueries().Select(query => 
    query.Where(x => x.FirstName.StartsWith("d") 
     && !x.IsRemoved) 
    .Select(x => x.FirstName) 
    .OrderBy(x => x.FirstName)); 

var results = Foo(queries); 

個人而言,我實在不明白需要結合這兩種方法。你可以創建一個方法,但它們確實是相當分離的概念,所以我沒有看到它的需要。如果你不希望他們合併雖然,那就是:

public static IEnumerable<TResult> Bar<TSource, TResult>(
    IEnumerable<IQueryable<TSource>> queries, 
    Func<IQueryable<TSource>, IQueryable<TResult>> selector) 
{ 

    return queries.Select(selector) 
     .AsParallel() 
     .Select(query => query.ToList()) 
     .SelectMany(results => results); 
} 

隨意,如果你想要麼FooBar擴展方法。另外,如果你打算使用它們,確實是更好地將它們重命名爲更好的東西。

4

首先 - 根據您當前的實施情況,沒有理由使用IQueryable<T> - 您可以使用IEnumerable<T>

然後,您可以寫這需要一個IEnumerable<IEnumerable<T>>Func<IEnumerable<T>, IEnumerable<U>>,建立一個結果的方法:

IEnumerable<IEnumerable<U>> QueryMultiple<T,U>(IEnumerable<IEnumerable<T>> inputs, Func<IEnumerable<T>,IEnumerable<U>> mapping) 
{ 
    return inputs.AsParallel().Select(i => mapping(i)); 
} 

然後,您可以使用它作爲:

void Run() 
{ 
    IEnumerable<IEnumerable<YourType>> inputs = GetYourObjects(); 

    Func<IEnumerable<YourType>, IEnumerable<YourType>> query = i => 
     i.Where(x => x.FirstName.StartsWith("d") && x.IsRemoved == false) 
     .Select(x => x.FirstName) 
     .OrderBy(x => x.FirstName); 

    var results = QueryMultiple(inputs, query); 
} 
+0

這很有趣,但我認爲這個問題的總體思路和查詢的用法是通過構建/生成表達式樹來減少對提供者的調用,這些表達式樹要被**評估/解析/處理你正在運行它/提供者;通過這種方式提供者可以採取這種(可能是複雜的)表達式,並以任何它被認爲是最優化的方式運行它一次。此代碼示例是否實現了這一點? –

+0

@BrettCaswell OPs代碼是LINQ到對象的所有東西,並在內存中處理,所以這不是一個問題。儘管如此,這在內存中起作用 - 所以它不能處理這個問題。 –

+0

感謝您的迴應,我想你是對的..我在想無論是SQL,Entity還是Object ..下劃線概念仍然是構建表達式樹。我看了一下PredicateBuilders和LINQkit,看起來可以創建Expression.Lambda ,它使用InvocationExpressions(invoke)來構建QueryExpression。有一個小小的欺騙(交換)與一個EntitySet擴展方法 –