2012-05-22 54 views
2

在T-SQL中,您可以使用CROSS APPLY從表中獲得左右表之間所有可能的差異。現在,我在C#中遇到以下情況,我希望有一種方法可以使用LINQ到對象來解決我的問題。交叉應用 - LINQ到對象

我有TestData對象(如下面)的列表,其類似於KeyValuePair<string, object>對象(只是KeyValue屬性): 鍵可以是一切,可以有具有相同鍵的多個對象。

IList<KeyValuePair<String, Object>> objects; 
// Content of list 
// # | Key | Value 
// 1 | "A" | 1 
// 2 | "A" | 2 
// 3 | "A" | 3 
// 4 | "B" | 4 
// 5 | "B" | 5 
// 6 | "C" | 6 
// 7 | "D" | 7 
// 8 | "D" | 8 

我還請鍵列表:

IList<String> requestedKeys = new List<string>() { "A", "D" }; 

現在我想有在requestedKeys列表中的鍵之間KeyValuePair對象的所有可能的組合。

IList<IList<KeyValuePair<String, Object>>> result = ... 
// Content of 'result' will be in this example 6 lists with each 2 KeyValuePair objects 
// # | "A" | "D" | (If there are more in the requestedKey list then there are more KeyValuePair items in the innerlist.) 
// 1 | 1 | 7 | 
// 2 | 2 | 7 | 
// 3 | 3 | 7 | 
// 4 | 1 | 8 | 
// 5 | 2 | 8 | 
// 6 | 3 | 8 | 

是否有可能使用LINQ-to-Objects解決我的問題。如果不是,你能否告訴我反正建立它的最有效的方法。


編輯1:
爲了更清楚的結果應該是什麼:
我想有一個LINQ到對象查詢是這樣的:
@Joanna感謝在末端多個from s,但問題是:使用此語法,您不能有動態數量from s。在我來說,我需要儘可能多的from S作爲在requestedKeys列表項

var result =  
    from listA in objects.Where(m => m.Key == "A") 
    from listD in objects.Where(m => m.Key == "D") 
    // from ..... 
    // from ..... 
    // overhere as many froms as items in 'requestedKeys' list 
select new [] { listA, listD /*, All other lists */ } 
+0

的http://博客。 msdn.com/b/ericlippert/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx –

+0

那麼,@喬安娜的回答有什麼問題? (和你的編輯?) – leppie

+0

@leppie它不夠動態。例如,如果我請求所有可能的A B和C組合,那麼你需要在結果列表中有第三個'from'和第三個KeyValuePair。所以我想知道如何使它變得動態。 – hwcverwe

回答

1

我找到了自己的解決方案:

它是在LINQ一個非常複雜的加入,因爲在requestKeys列表中每個項目需要額外的交叉連接。關於給出的示例列表,結果應該是objects.Count(m => m.Key == "A") * objects.Count(m => m.Key == "D")(結果是3 * 2 = 6)。列表中的每個額外項目都會導致整個結果集的額外倍增。

所以這就是結果:

// The result list 
IEnumerable<IList<KeyValuePair<char, int>>> result; 

// If there are no requestedKeys there is no result expected 
if(requestedKeys.Count() > 0) 
{ 
    // Loop through all request keys to cross join them together 
    foreach (var key in requestedKeys) 
    { 
     if (result == null) 
     { 
      // First time the innerlist List<KeyValuePair<char, int>> will contain 1 item 
      // Don't forget to use ToList() otherwise the expression will be executed to late. 
      result = objects.Where(m => m.Key == key).Select(m => new List<KeyValuePair<char, int>>() { m }).ToList(); 
     } 
     else 
     { 
      // Except for the first time the next subresult will be cross joined 
      var subresult = objects.Where(m => m.Key == key).Select(m => new List<KeyValuePair<char, int>>() { m }); 
      result = result.Join(
       subresult, 
       l1 => 0, // This and the next parameter does the cross join trick 
       l2 => 0, // This and the previous parameter does the cross join trick 
       (l1, l2) => l1.Concat(l2).ToList() // Concat both lists which causes previous list plus one new added item 
       ).ToList(); // Again don't forget to 'materialize' (I don't know it is called materialization in LINQ-to-Objects 
          // but it has simular behaviors because the expression needs to be executed right away) 
     } 
    }   
} 
return result; 

遺憾的是,沒有完全LINQ因此,如果有人知道一個更好的解決方案。請評論我還是回答我的問題:)

+0

當我看到像這樣的怪異解決方案時,我想刮掉我的眼睛。該查詢非常簡單: requestedKeys.GroupJoin(objects,key => key,m => m.Key,(key,ms)=> new {Key,ms})其中(keygrp => keygrp.Any() ) –

+0

@MortenGormMadsen。您的答案不能提供請求的結果。這個答案是在2012年給出的,希望現在有更好的方法。我仍然希望你能找到更好的解決方案,因爲我同意這段代碼不可讀 – hwcverwe

3

東西沿着這些路線應該工作:

var filtered = objects 
     .Where(o => requestedKeys.Contains(o.Key)); 

var crossJoined = from el1 in filtered 
        from el2 in filtered 
        select new [] {el1, el2}; 

交叉聯接通過級聯多個from條款來實現的。

編輯:

在這種情況下,我想不出這樣做比你在你的編輯開始的事情有一個更簡單的方法。唯一缺少的就是選擇值:

var result =  
    from listA in objects.Where(m => m.Key == "A").Select(m => m.Value) 
    from listD in objects.Where(m => m.Key == "D").Select(m => m.Value) 
    // from ..... 
    // from ..... 
    // overhere as many froms as items in 'requestedKeys' list 
select new [] { listA, listD /*, All other lists */ } 
+0

感謝您的回答。我很高興聽到我可以使用多個從子句,但是現在您在'filtered'中執行所有值之間的交叉連接。但是我想爲'requestedKeys'中的每個項目都有一個單獨的列表來交叉應用。看看我最後的編輯。 – hwcverwe

+0

@hwcverwe - 我編輯了你發佈的代碼來選擇值 - 不知道這是否已經是你想要的? –

+0

問題是你有一個動態數量的'from's和一個動態數量的項目的結果innerlist。 (「from」的數量和內部列表中的項目數量等於請求的鍵的數量)。我今天解決了這個問題。看看我的答案。這比一開始我想的要複雜得多。感謝您的幫助 – hwcverwe

1

用戶通過這種方式可以genreate SQL交叉應用:

var comments = AppCommentRepository.Where(com => com.iAction > -1 && productIds.Contains(com.sProductId)) 
      .GroupBy(c => c.sProductId) 
      .SelectMany(p => p.OrderByDescending(cc => cc.dAddTime).Take(commentNum)).ToList(); 

最後,SQL是:

SELECT [t3].[iCommentId], .....FROM (
     SELECT [t0].[sProductId] 
     FROM [dbo].[App_Comment] AS [t0] 
     WHERE ([t0].[iAction] > -1) --AND ([t0].[sProductId] IN (@p1)) 
      GROUP BY [t0].[sProductId] 
     ) AS [t1] 
CROSS APPLY (
    SELECT TOP (2) [t2].[iCommentId],...... 
    FROM [dbo].[App_Comment] AS [t2] 
    WHERE ([t1].[sProductId] = [t2].[sProductId]) AND ([t2].[iAction] > -1) 
-- AND ([t2].sProductId] IN (@p1)) 
    ORDER BY [t2].[dAddTime] DESC 
    ) AS [t3] 
ORDER BY [t3].sProductId DESC 
0
objects 
.Join(requestedKeys, o => o.Key, rk => rk, (o, rk) => o) 
.SelectMany(o => requestedKeys.Select(k => new {Key = k, Value = o.Value}));