Entity Framework v6 GroupBy 丢失原始排序

Posted

技术标签:

【中文标题】Entity Framework v6 GroupBy 丢失原始排序【英文标题】:Entity Framework v6 GroupBy Losing Original Ordering 【发布时间】:2016-02-26 13:08:26 【问题描述】:

我有以下查询部分(这是更大查询的结尾 - queryBuilder 是一个 IQueryable

            var results = queryBuilder
                          .OrderBy(x => x.Vehicle.RangeId)
                          .ThenBy(x => x.Rate.Rental)
                          .GroupBy(x => x.Vehicle.RangeId)
                          .Select(x => x.FirstOrDefault())
                          .OrderBy(x => x.Rate.Rental);

它使用具有 Vehicle 对象和 Rental 对象的对象。大约有 12,000 辆车,分为大约 40 个范围(RangeId - Indexed int 规定了这一点)。

上面的查询工作正常,但没有达到我的预期。我希望它先按 RangeId 订购车辆,然后按租金(最低租金优先)。

然后按 RangeId 对它们进行分组,并从组中选择第一个,这“应该”是最便宜的租金,因为它在调用 groupby 之前被订购。

但是。它不是。它只是随机得到一个,没有顺序。有时它是第二便宜的。有时第 5 次等等。出于某种原因,GroupBy 不遵守原始订单!

我可以通过执行以下操作来完成这项工作。 但是表现绝对糟糕,需要很长时间才能完成。

            var results = queryBuilder
                          .OrderBy(x => x.Vehicle.RangeId)
                          .ThenBy(x => x.Rate.Rental)
                          .GroupBy(x => x.Vehicle.RangeId)
                          .Select(x => x.OrderBy(o => o.Rate.Rental).FirstOrDefault())
                          .OrderBy(x => x.Rate.Rental);

任何帮助或指针将不胜感激。我无法弄清楚为什么原始查询没有保持一切井井有条。

【问题讨论】:

先尝试使用 GroupBy,然后对各个组进行排序。也应该提高性能 这就是我在第二个示例中所做的,不是吗?这方面的表现很糟糕。 我应该补充一下,我已经尝试删除 GroupBy 之前的所有订单查询,但这对糟糕的性能没有影响。 在顶部查询中,我认为是 Select 错误。您可以尝试使用 SelectMany(x => x.FirstOrDefault()) 吗? 那时我不能这么多选择。 VS 抛出错误,无法编译。 【参考方案1】:

我希望它先按 RangeId 订购车辆,然后再按租赁方式订购车辆

在 LINQ to Entities 查询中,GroupBy 之前的任何排序都会被忽略。您甚至不会在执行的 SQL 中看到它。这是因为实体框架采用分组表达式来排序(在您的情况下为x => x.Vehicle.RangeId)。这是为什么呢?

LINQ 的GroupBy 看似与SQL 的GROUP BY 相似,但实际上却大不相同。

GROUP BY 在 SQL 中是“破坏性的”,我的意思是除了 GROUP BY 中的列之外的任何信息都会丢失(除了聚合表达式)。如果你这样做......

SELECT Brand, COUNT(*) 
FROM Cars
GROUP BY Brand

...您只会看到Brand 及其计数。您看不到组中的汽车。

这正是 LINQ 的GroupBy 所做的:它生成完整 对象组。原始数据中的所有信息仍然存在。您会看到按品牌分组的汽车。

这意味着将GroupBy 转换为GROUP BY 的ORM 很难构建结果集。 LINQ to SQL 就是这样做的。它首先执行GROUP BY 查询,然后需要单独的查询(实际上是每组一个)来弥补“丢失”的数据。

EF 以不同的方式实现GroupBy。它在一次查询中获取所有数据,然后在内存中构建组。您不会在生成的 SQL 中看到 GROUP BY。您会看到 ORDER BY。我认为 EF 更喜欢排序的 SQL 查询结果,以便在内存中进行更有效的处理。 (我可以想象与管道中的其他 LINQ 语句更好地结合)。

这就是为什么GroupBy 之前的任何排序都被忽略的原因。以及为什么只能在分组后应用排序。

表现非常糟糕

从这里很难说这是为什么。也许您可以在内存中进行排序:

var results = queryBuilder
              .GroupBy(x => x.Vehicle.RangeId)
              .Select(x => x.OrderBy(o => o.Rate.Rental).FirstOrDefault())
              .Select(o => new  o.Rate.Rental, o 
              .AsEnumerable()
              .OrderBy(x => x.Rental);

但这也可能是索引问题。如果Rate.Rental 上没有适当的索引,则按该列排序会很昂贵。

【讨论】:

非常棒的回复谢谢。感谢您花时间解释它。

以上是关于Entity Framework v6 GroupBy 丢失原始排序的主要内容,如果未能解决你的问题,请参考以下文章

SQL to Entity Framework Count Group-By

如何在 Entity Framework Group By 结果中包含属性

Entity Framework DB First - 命名导航属性

MVC5 Entity Framework学习之Entity Framework高级功能

Entity Framework 学习系列 - 认识理解Entity Framework

Entity Framework Code-First(23):Entity Framework Power Tools