EF Core 2.1 GROUP BY,然后在每个组中选择第一项

时间:2018-08-20 05:19:47

标签: c# group-by entity-framework-core

让我们想象一个论坛,其中包含主题和帖子列表。 我想获取主题列表以及每个主题的最新帖子标题(按日期)。

有没有一种方法可以使用EF Core(2.1)? 在SQL中,可以像

SELECT Posts.Title, Posts.CreatedDate, Posts.TopicId FROM 
  (SELECT Max(CreatedDate), TopicId FROM Posts GROUP BY TopicId) lastPosts
JOIN Posts ON Posts.CreatedDate = lastPosts.CreatedDate AND Posts.TopicId = lastPosts.TopicId

在EFCore中,我可以选择LastDates

_context.Posts.GroupBy(x => x.TopicId, (x, y) => new
            {
                CreatedDate = y.Max(z => z.CreatedDate),
                TopicId = x,
            });

如果我运行.ToList(),查询将正确翻译为GROUP BY。 但是我不能走的更远。 以下内容在内存中执行,而不是在SQL中执行(导致SELECT * FROM Posts):

            .GroupBy(...)
            .Select(x => new
            {
                x.TopicId,
                Post = x.Posts.Where(z => z.CreatedDate == x.CreatedDate)
                //Post = x.Posts.FirstOrDefault(z => z.CreatedDate == x.CreatedDate)
            })

尝试加入JOIN会导致NotSupportedException(无法解析表达式):

.GroupBy(...)
.Join(_context.Posts,
                    (x, y) => x.TopicId == y.TopicId && x.CreatedDate == y.CreatedDate,
                    (x, post) => new
                    {
                        post.Title,
                        post.CreatedDate,
                    })

我知道我可以使用SELECT N + 1(针对每个主题运行一个单独的查询)来做到这一点,但我想避免这种情况。

3 个答案:

答案 0 :(得分:5)

基本上我现在要做的就是跑步后

var topics = _context.Posts.GroupBy(x => x.TopicId, (x, y) => new
            {
                CreatedDate = y.Max(z => z.CreatedDate),
                TopicId = x,
            }).ToList();

我建立以下查询:

Expression<Func<Post, bool>> lastPostsQuery = post => false;
foreach (var topic in topics) 
{
    lastPostsQuery = lastPostsQuery.Or(post => post.TopicId == topic.TopicId && post.CreatedDate = topic.CreatedDate); //.Or is implemented in PredicateBuilder
}
var lastPosts = _context.Posts.Where(lastPostsQuery).ToList();

哪个查询(而不是N个查询)像SELECT * FROM Posts WHERE (Posts.TopicId == 1 AND Posts.CreatedDate = '2017-08-01') OR (Posts.TopicId == 2 AND Posts.CreatedDate = '2017-08-02') OR ...

效率不是很高,但是由于每页的主题数量很少,因此可以解决问题。

答案 1 :(得分:4)

我不知道从哪个版本的EFCore开始,但是现在有一个更简单的单查询替代方法:

context.Topic
   .SelectMany(topic => topic.Posts.OrderByDescending(z => z.CreatedDate).Take(1),
        (topic, post) => new {topic.Id, topic.Title, post.Text, post.CreatedDate})
   .OrderByDescending(x => x.CreatedDate)
   .ToList();

答案 2 :(得分:1)

在EF Core 2.1中,GroupBy LINQ运算符仅在大多数常见情况下支持转换为SQL GROUP BY子句。汇总函数,例如sum,max ...

linq-groupby-translation

您可以在EF Core全面支持之前使用Dapper