Sha*_*dix 3 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
Run Code Online (Sandbox Code Playgroud)
在EFCore中,我可以选择LastDates
_context.Posts.GroupBy(x => x.TopicId, (x, y) => new
{
CreatedDate = y.Max(z => z.CreatedDate),
TopicId = x,
});
Run Code Online (Sandbox Code Playgroud)
如果我运行.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)
})
Run Code Online (Sandbox Code Playgroud)
尝试加入JOIN会导致NotSupportedException(无法解析表达式):
.GroupBy(...)
.Join(_context.Posts,
(x, y) => x.TopicId == y.TopicId && x.CreatedDate == y.CreatedDate,
(x, post) => new
{
post.Title,
post.CreatedDate,
})
Run Code Online (Sandbox Code Playgroud)
我知道我可以使用SELECT N + 1(针对每个主题运行单独的查询)来做到这一点,但我想避免这种情况。
Sha*_*dix 10
我不知道从哪个版本的 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();
Run Code Online (Sandbox Code Playgroud)
基本上我现在要做的就是跑步后
var topics = _context.Posts.GroupBy(x => x.TopicId, (x, y) => new
{
CreatedDate = y.Max(z => z.CreatedDate),
TopicId = x,
}).ToList();
Run Code Online (Sandbox Code Playgroud)
我建立以下查询:
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();
Run Code Online (Sandbox Code Playgroud)
结果是一个查询(而不是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 ...。
效率不是很高,但是由于每页的主题数量很少,因此可以解决问题。