Linq得到不同的有序元素而没有.GroupBy()

时间:2017-05-27 13:58:15

标签: c# linq linq-to-entities

鉴于IQueryable< Product>在数据库中使用以下值(值是假的,以及实体和字段的名称):

Id     | CategoryName | Price
------ | -------------|-------
1      | car          | 10000
2      | boat         | 15000
3      | boat         | 20000
4      | car          | 5000
5      | boat         | 30000
6      | food         | 100
7      | car          | 15000
8      | food         | 200

我需要转换此IQueryable< Product>到IOrderedQueryable< Product>与其类别中最昂贵的产品的价值,按价格降序排序。所以它看起来像:

Id     | CategoryName | Price
------ | -------------|-------
5      | boat         | 30000
7      | car          | 15000
8      | food         | 200

由于IQueryable提供了在底层SQL查询中执行分组和排序的能力(并且真实表包含大约10k行,其中有关于如何对值进行分组的更复杂的标准),我想要的关键是< EM>性能

我已经有了一个工作解决方案,如下所示:

IQueryable<Product> queryable = ...;
var result = queryable
    .GroupBy(
        x => x.CategoryName,
        (_, productsInGroup) => productsInGroup
            .FirstOrDefault(x => x.Price == productsInGroup.Max(p => p.Price))
    .OrderByDescending(x => x.Price);

此解决方案导致使用GROUP BY和嵌套选择进行长查询(计算MAX(p.Price),选择组中的正确行等等)

没有GroupBy有没有能力做到这一点? 我想要像:

var result = queryable
    .OrderByDescending(x => x.Price)
    .ExcludeRowsWithDuplicateCategoryName(); // Distinct by predicate

以便此类查询首先按降序对值进行排序:

Id     | CategoryName | Price
------ | -------------|-------
5      | boat         | 30000
3      | boat         | 20000
7      | car          | 15000
2      | boat         | 15000
1      | car          | 10000
4      | car          | 5000
8      | food         | 200
6      | food         | 100

然后只是以某种方式排除第3,2,1,4,6行,因为它们已经在上面的行中出现了CategoryName。

我该怎么做?

2 个答案:

答案 0 :(得分:2)

我想到的唯一选择是使用 self anti join 和比较条件(或基于NOT EXISTS的查询),如下所示:

var result = queryable
    .Where(x => !queryable.Any(y => y.CategoryName == x.CategoryName && y.Price > x.Price))
    .OrderByDescending(x => x.Price)
    .ToList();

它是否具有更高的性能取决于具体的可查询和数据库表索引。

值得一提的是,上述内容并非完全等效,因为如果每组有两个或更多元素,每个类别会返回多个记录,因此确切的等价物需要额外的标准,如:

y => y.CategoryName == x.CategoryName &&
    (y.Price > x.Price || (y.Price == x.Price && y.Id > x.Id))

答案 1 :(得分:1)

从我可以在帖子中告诉查询: 首先找到组中的最高价格

productsInGroup.Max(p => p.Price)
然后

找到具有匹配价格的第一个元素。

FirstOrDefault(x => x.Price == productsInGroup.Max(p => p.Price))

不确定您是否可以实际跳过该组,但在按价格订购后从组中取出第一个元素可能更简单。

var result = Products.GroupBy(f => f.CategoryName).Select(gr => gr.OrderByDescending(p => p.Price).First() );