我正在使用EF 6编写查询(CQRS模式),这些查询将由我的控制器使用。除了视图模型,我还想返回一些额外的数据给客户端,以启用适当的分页(例如。总计数、返回计数和剩余计数)。
我最初的查询如下:
_posts //DbSet
.Where(x => x.Status.Equals(PostStatus.Published))
.OrderByDescending(x => x.CreatedAt)
.Where(x => x.CreatedAt < createdAtCursor) //cursor pagination based on created at date
.Include(x => x.Translations
.Where(x => x.Language.Equals(language)))
.Where(x => x.Translations != null) //excluding all posts without translation in a given language
.Take(pageSize)
.Select(x => x.ToPreviewModel())
.AsNoTracking();
所以我想出了以下解决方案:
选项1:拆分查询,在其中运行CountAsync()
var allPublishedPosts = _posts
.Where(x => x.Status.Equals(PostStatus.Published))
.Include(x => x.Translations
.Where(x => x.Language.Equals(language)))
.Where(x => x.Translations != null);
int totalCount = await allPublishedPosts
.CountAsync()
.ConfigureAwait(false);
var olderThanPublishedPosts = allPublishedPosts
.Where(x => x.CreatedAt < createdAtCursor);
int olderThanCount = await olderThanPublishedPosts
.CountAsync()
.ConfigureAwait(false);
var returnedPublishedPosts = olderThanPublishedPosts
.OrderByDescending(x => x.CreatedAt)
.Take(pageSize)
.Select(x => x.ToPreviewModel())
.AsNoTracking();
int returnedCount = await returnedPublishedPosts
.CountAsync()
.ConfigureAwait(false);
在这里,我担心Include()
在查询的顶部--我想在比原始查询更大的集合上运行这个命令可能会花费很大,而且我还需要调用db 3次,然后一个接一个地等待每个CountAsync()
完成。
选项2:拆分查询,重写它以DbContext
//DbSet
作为参数,并在多个上下文中并行运行CountAsync()
。
IQueryable<PostBase> allPublishedPostsQuery(DbSet<PostBase> posts) =>
posts
.Where(x => x.Status.Equals(PostStatus.Published))
.Include(x => x.Translations
.Where(x => x.Language.Equals(language)))
.Where(x => x.Translations != null);
//...so on for the other parts of the query
int[]? countResults;
using (var context1 = _contextFactory.CreateDbContext())
using (var context2 = _contextFactory.CreateDbContext())
using (var context3 = _contextFactory.CreateDbContext())
{
countResults = await Task.WhenAll(
allPublishedPostsQuery(context1.Posts)
.CountAsync(),
olderThanPublishedPostsQuery(context2.Posts)
.CountAsync(),
returnedPublishedPostsQuery(context3.Posts)
.CountAsync())
.ConfigureAwait(false);
};
int totalPostsCount = countResults[0];
int olderThanPostsCount = countResults[1];
int returnedPostsCount = countResults[2];
在我看来有点过分,但我不知道表现如何。遗憾的是,我们不能在一个单一的环境中做到这一点。
选项3:?理想情况下,我希望在一个完整的查询中完成它,但我不确定这是否可行。
最终,我可以把所有的计算都留给前端,只提供总数,但我仍然好奇将来如何以最好的方式解决它,并可能提高查询本身的效率。
发布于 2022-01-27 05:45:52
当涉及到分页时,大多数控件只需要一个行计数、一个页面大小和一个页面#。在大多数情况下,EF是相当有效的,当你告诉它去取一个计数。不应该需要执行多个计数。我通常只需将OrderBy*
子句从查询中删除,进行计数,然后追加OrderBy*
子句并获取数据页。
尽管如此,使用非常大的数据集和用于筛选数据的潜在动态标准,计数查询可能会变得相当昂贵。如果您所面临的情况是,您可能会看到非常大的计数值和缓慢的计数查询,那么我可以建议您做一点“欺骗”:
假设您的分页控件设置为50页大小,并且每次显示10个页面选择器。1-10然后一个>>如果更多的页面.
在没有OrderBy/分页的情况下构建查询。根据选定的页码,确定需要加载多少组10页:
var pages = ((pageNumber / 10) +1) * 10;
例如,如果加载页面1,页= 10,如果加载页11或12,则页= 20。
接下来,根据所需的+1行限制您的总行,并根据此数字进行计数。
var count = query.Take(pages * pageSize + 1).Count();
最后,对照pages * pageSize + 1检查计数,如果它相等,那么您有更多值得加载的页面,否则计数将反映记录的实际数量。
这给你的是一个自适应的页面计数。当加载结果的第一页时,它最多会计算500行(页面大小为50,10页)。如果有<= 500行,分页控件将显示正确的页面#,我们可以显示实际计数。如果有> 500行,则分页将显示10页加上预期的第11页(在下一页>>控件中呈现),我们将行计数显示为类似于"500+“或”至少500“的内容,而不是特定的计数。如果客户端出于任何原因想要获得准确的行计数,我将将其呈现为一个超链接,它实际上将执行一个完整的计数并更新总数。如果用户选择>>下一页,请求页面#11,则计数检查会限制在20页,而不是10页。如果有超过1000个结果,呈现将显示页面11-20 /w,>>下一页,一行计数"1000+“。
这种方法的限制是,除非用户明确请求,否则您不能赋予用户访问特定页面或显示每个查询的准确行计数的能力。但是,这与改进最典型的用户期望在第一页或几页上搜索和查找结果的场景是相提并论的,在改进搜索标准之前,很少会超出这10页。俗话说:“最好的地方隐藏身体是在谷歌的搜索结果第二页。”对于非常大或复杂的数据集,行计数和精确的分页结果可能会花费相当大的代价,而不会带来什么好处。
https://stackoverflow.com/questions/70872482
复制相似问题