2012-05-22 51 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}));