【问题标题】:LINQ GroupBy, whilst keeping all object fieldsLINQ GroupBy,同时保留所有对象字段
【发布时间】:2012-11-15 23:32:16
【问题描述】:

我目前有这个示例数据表:

ID  | Policy ID     |   History ID  | Policy name
1   |   1           |    0          | Test
2   |   1           |    1          | Test
3   |   2           |    0          | Test1
4   |   2           |    1          | Test1

除此之外,我想按策略 ID 和历史 ID (MAX) 分组,所以我要保留的记录是 ID 的 2 和 4:

   ID   | Policy ID     |   History ID  | Policy name
    2   |   1           |    1          | Test
    4   |   2           |    1          | Test1

我尝试在 LINQ 中执行此操作,但每次都遇到同样的问题。我可以对我的实体进行分组,但总是将它们分组到一个我必须重新定义属性的组中,而不是将它们从我的 Policy 对象中保留下来。如:

var policies = _context.Policies.GroupBy(a => a.intPolicyId)
                                            .Select(group => new {
                                                PolicyID = group.Key,
                                                HistoryID = group.Max(a => a.intHistoryID)
                                            });

这只是列出了其中包含“策略 ID”和“历史 ID”的对象列表。我想要从 Policies 对象返回的所有属性,而不必重新定义它们,因为该对象中有大约 50 多个属性。

我试过了:

        var policies = _context.Policies.GroupBy(a => a.intPolicyId)
                                                    .Select(group => new {
                                                        PolicyID = group.Key,
                                                        HistoryID = group.Max(a => a.intHistoryID)
                                                        PolicyObject = group;
                                                    });

但这会出错。

有什么想法吗?

【问题讨论】:

  • 这里我有点困惑:如果 intPolicyID 是 Policies 表的主键,为什么要按它分组?它将始终返回表中的每一行,因为每个 intPolicyID 都是唯一的。
  • 我认为这只是我自己一个绝望的编码失误才能让它工作。实际上,我猜它只会按每一行的历史 ID 分组。
  • 好的。在这种情况下,您也不能按历史 ID 分组。不确定您是否有真正想要分组的客户 ID 或其他字段。但这是此过程中的第 1 步。
  • 重新阅读您的问题我认为您想要的是每个政策,您想要最新的“历史”对象,并且您假设每个政策的最大历史 ID 是最新的。但是,历史记录项是否保存在策略表中?它们很可能保存在“历史记录”表或类似的东西中。您可能应该 _context.Histories.GroupBy(a => a.intPolicyId),因为 intPolicyID 可以在历史表中出现多次。这只是一个猜测,因为我看不到您的架构。
  • 哦,实际上,您可能在这里找到了可以加快查询速度的东西,我也会尝试实施这种方法并检查速度...

标签: c# .net linq lambda


【解决方案1】:

按复合键分组

_context.Policies.GroupBy(a => new {a.intPolicyId, *other fields*}).Select(
    group=> new {
        PolicyId = group.Key.intPolicyId,
        HistoryId = group.Max(intHistoryId),
        *other fields*
    }
);

另一种方法 - 获取历史记录,而不是加入其余数据,类似这样(不能开箱即用,需要一些改进)

var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
                                            PolicyID = group.Key,
                                            HistoryID = group.Max(a => a.intHistoryID)
                                        });

var finalData = from h in historyIDs
                join p in _context.Policies on h.intPolicyId equals p.intPolicyId
                select new {h.HistoryId, *all other policy fields*}

还有另一种方式,更简单,不需要大量输入:):

var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
                                            PolicyID = group.Key,
                                            HistoryID = group.Max(a => a.intHistoryID)
                                        });

var finalData = from h in historyIDs
                join p in _context.Policies on h.PolicyId equals p.intPolicyId && h.HistoryId equals p.HistoryId
                select p

基本上它有点等价于下面的 SQL 查询:

select p.*
from Policy p
inner join (
    select pi.policyId, max(pi.historyId)
    from Policy pi
    group by pi.policyId
) pp on pp.policyId = p.policyId and pp.historyId = p.historyId

【讨论】:

  • 感谢您的回答,但这与我目前得到的不一样,我必须按顺序输入 其他字段(因此有 50 多个属性)让它工作?
  • 第二个选项需要返回数据库,因此以单一方法进行 2 次往返。非常感谢您在这里的帮助,但肯定有一种方法可以在一次旅行中做到这一点吗?
  • 不太对。第一个查询不会立即执行,它只是嵌入到第二个查询的查询树中。所以它真的应该是一次到数据库的往返。
  • 认为在检查您的代码后这可能会起作用 - 将在 5 后报告!
  • 只要您不枚举 historyIDs 变量,它就不会构建/运行查询。所以性能不应该受到影响;数据库查询不会发生,直到您在 finalData/historyIDs 上调用 .ToList() 或 foreach。
【解决方案2】:

在 LINQ to Objects 中,我会这样做

var policies = _context.Policies
    .GroupBy(a => a.intPolicyId)
    .Select(g => g.OrderByDescending(p => p.intHistoryID).First());

但您的 _context 暗示可能涉及数据库,我不能 100% 确定这会转化。

基本上,它按您所期望的策略 ID 分组,然后在每个组中按历史 ID 排序,并从每个组中选择具有最高历史 ID 的行。它返回的类型与 Policies 中的类型完全相同。

【讨论】:

  • 感谢您的回复,但我在使该解决方案也能正常工作时遇到了问题。错误是查询无法编译,并收到错误消息:“函数评估已禁用,因为先前的函数评估超时。您必须继续执行才能重新启用函数评估。”,即 .FirstOrDefault(), .First( ) 完全失败了。
  • 这很奇怪,我原以为这会起作用 :( 您可以尝试将 OrderBy 移动到 before GroupBy 但理论上它应该不会赚太多区别。
  • 发生超时的原因可能是由于数据集/表较大。基本上,对于表中的每一行,您都使用 .First() 调用第二次查询数据库。 First() 调用强制对 GroupBy/Select 链中的每个对象进行评估,这只是对任何实质性内容的性能破坏者。
  • @Richthofen 在这种情况下,我希望 LINQ 提供程序能够更好地处理这个问题;我希望 OrderBy...First 部分被包含为子查询,而不是一批后续查询。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 2011-03-23
  • 1970-01-01
  • 2021-02-26
  • 1970-01-01
相关资源
最近更新 更多