如何为每个键进入 LINQ 一行

Posted

技术标签:

【中文标题】如何为每个键进入 LINQ 一行【英文标题】:How to get in LINQ one row for each key 【发布时间】:2018-07-30 12:26:29 【问题描述】:

我有以下清单:

EMP_ID | UPDATED_DATE |标记 ------ | ------------ | ---- 111 | 2015 年 1 月 1 日 | 99 111 | 2013 年 1 月 1 日 | 85 111 | 2017 年 1 月 1 日 | 80 222 | 2011 年 1 月 1 日 | 70 222 | 2015 年 1 月 1 日 | 55 222 | 2002 年 1 月 1 日 | 60

我必须为每个 ID 选择一行,最新的 UPDATED_DATE, 在我们的等:

EMP_ID | UPDATED_DATE |标记 ------ | ------------ | ---- 111 | 2017 年 1 月 1 日 | 80 222 | 2015 年 1 月 1 日 | 55

这是订单代码:

empMarksList.OrderBy(x=>x.EMP_ID).ThenBy(y=>y.UPDATED_DATE)

【问题讨论】:

empMarksList.GroupBy(x => x.Id).Select(x=>x.OrderByDescending(y=>y.UPDATED_DATE).First()) ? OrderBy(x=>x.Id) Id 来自哪里? 运气不好,你的答案在哪里??这是一个很好的答案! Select values with max date for each ID的可能重复 你在这里使用EntityFramework吗?我可以在标签中看到它。 【参考方案1】:

使用GroupBy:

var items = empMarksList
                   .GroupBy(e => e.EMP_ID)
                   .Select(grp => grp.OrderByDescending(v => v.UPDATED_DATE).First());

或者如果你想要一个字典:

var dict = empMarksList
              .GroupBy(e => e.EMP_ID)
              .ToDictionary(grp => grp.Key,
                            grp => grp.OrderByDescending(v => v.UPDATED_DATE).First());

【讨论】:

@arekzyla - 在这段代码中.First() 非常好,因为.GroupBy 总是保证至少一个结果。 @Enigmativity 我得到一个例外:'方法'First'只能用作最终查询操作。考虑在这种情况下使用“FirstOrDefault”方法。对于 Linq-To-Entities,它不起作用。此外,GROUP BY 不会从此查询中生成,而只会生成 OUTER APPLYWHERE inner.Id = outer.Id 之类的东西。 @arekzyla 查询应该无关紧要,Here is a working example implementation。 @AmirPopovich 我不是 100% 确定,但检查 Max 应该比 OrderByDescending 更快,不是吗?例如。 grp.First(v => v.UPDATED_DATE == grp.Max(g => g.UPDATED_DATE)) @Marie 但是您的示例使用 Linq-to-Objects 而 OP 使用 EntityFramework(如您在标签中看到的那样),因此 Linq-To-Entities 使用提供程序,您可以使用First 作为最终查询操作。【参考方案2】:

我更喜欢这个变体,但这和阿米尔的回答是一样的:

var query =
    empMarksList
        .GroupBy(x => x.EMP_ID)
        .SelectMany(x => x.OrderByDescending(y => y.UPDATED_DATE).Take(1));

【讨论】:

首选,因为简单地取 1 而不是执行完整的结果然后返回第一个(第一个)项目。 @ItiTyagi - .First().Take(1) 都只返回第一项 - 都不执行完整的结果。 .Take(1) 更好,因为如果它之前的查询返回一个空列表,那么它不会抛出。 “因为如果之前的查询返回”是什么意思? 如果您担心一个空列表,您可以使用FirstOrDefault,它具有相同的结果,但在语义上更正确。在这种情况下,您的枚举由 GroupBy 返回,我 99% 确信 GroupBy 不能返回空列表。这没有任何意义。 @ItiTyagi - 我说“如果它之前的查询返回一个空列表”只是作为一个假设的评论。在这种情况下它不能,但是如果插入了.Where,那么它是可能的,那么最好养成一个好习惯。然后.FirstOrDefault 将提供不同的语义。【参考方案3】:

另一种选择是:

var items = context.EmpMarks
    .GroupBy(e => e.EMP_ID, (k, g) => g
        .FirstOrDefault(e => g.Max(v => v.UPDATED_DATE) == e.UPDATED_DATE));

实际上应该在 SQL 中生成GROUP BY

【讨论】:

【参考方案4】:

你可以这样使用:

var result = empMarksList.GroupBy(x => x.Id)
    .Select(g => 
        g.Aggregate((a, x) => a == null || a.UPDATRED_DATE < x.UPDATRED_DATE ? x : a));

这比使用OrderBy要麻烦一些,但是这样你就不会订购所有的子集合了,这有点矫枉过正,而且会占用更多的资源。

编辑: 在@arekzyla 的回答之后,我意识到我的选项也可以这样写:

var items = empMarksList.GroupBy(
   x => x.Id,
   (k, g) => g.Aggregate((a, x) => a == null || a.UPDATRED_DATE < x.UPDATRED_DATE ? x : a));

它的可读性较差,但在子集合上会有一个集合演练,而不是两个,这在大多数情况下可以忽略不计。

我不确定在什么情况下生成的 SQL 会更优化,因此可能值得检查。

【讨论】:

以上是关于如何为每个键进入 LINQ 一行的主要内容,如果未能解决你的问题,请参考以下文章

如何为每个给定的外键列表获取 n 项?

如何为每个键值选择具有最新时间戳的行?

如何为 sql 编写 lambda (linq) 表达式?

如何为以下 Scenerio 编写 linq 查询 [关闭]

如何为 Like 创建 System.Linq.Expressions.Expression?

如何为存在的每一行向 Spark 数据框中添加新列?