[英]Count total size of records before paging and return them after paging without hitting the database twice
我有一个分页方法,它获取页面并限制,然后将它们应用于给定的集合(可能会应用预过滤器/查询),然后将分页应用于它,因此无需在服务器端执行任何操作从计算记录到应用分页(只计算最终结果,无论如何都存储在 memory 中)
public async Task<PagingServiceResponse<T>> Apply<T>(IQueryable<T> set)
{
var httpQuery = _accessor.HttpContext.Request.Query;
var pageValue = httpQuery.LastOrDefault(x => x.Key == "page").Value;
if (pageValue.Count > 0) int.TryParse(pageValue, out _page);
var limitValue = httpQuery.LastOrDefault(x => x.Key == "limit").Value;
if (limitValue.Count > 0) int.TryParse(limitValue, out _limit);
if (_limit > 1000 || _limit <= 0) _limit = 1000;
if (_page <= 0) _page = 1;
_size = await set.CountAsync();
set = set.Take(_limit);
if (_page > 1) set = set.Skip(_page * _limit);
var data = await set.ToListAsync();
var currentSize = data.Count;
return new PagingServiceResponse<T>
{
Data = data,
Size = _size,
CurrentSize = currentSize,
Page = _page,
PerPage = _limit
};
}
所以这里的问题是这会命中数据库两次,以检查总计数( CountAsync
)并接收数据( ToListAsync
)
我试图不这样做,因为它执行了两次查询,这不是纯查询,有过滤器操作应用于它。
如果对另一种方法或某事有任何建议,我会全力以赴。
我正在使用 PostgreSQL 和实体框架核心(npgsql)
不,分页的整个前提是您需要在获取总记录的子集之前知道完整的行数。 可以在 1 个查询命中中完成的唯一方法是加载所有记录。 (对于大型套装来说,这是一个更糟糕的选择::)
我看到的一个问题是您使用Take
来限制行数(0 <= 1000?)然后跳过页面大小和页面#? 对我来说,如果限制是 1000 并且您的页面大小是 25,并且您正在加载第一页,这不会返回 1000 行吗? (而不是第一页的 25?)通常我希望分页查询更像:
var pagedData = set.Skip(page * pageSize).Take(pageSize).ToList();
其中page
是从 0 开始的。 (0 = 第 1 页)。 这确保了最多只能拉回 25 行。
您可以做一些事情来进一步降低分页查询和获取计数的成本:
构造您的查询构建和执行,以便在获得Count
后发生 Ordering 和 Projections ( Select
/ ProjectTo
)。
确保上下文是短暂的和“新鲜的”。 这不会加快计数,但加载子集的速度会越慢,跟踪的实体越多。
当不需要准确计数时,提供一个粗略的计数,可以作为用户 select 进一步扩展,或者可以选择检索完整计数。
粗略计数类似于 Google 搜索给出的近似值,而不是实际的结果计数。 我使用的相对简单的技术是获取当前页面大小和寻呼机显示的页面数。 需要调整分页控件以不显示导航到“最后”页面,并且还需要调整显示记录数。
因此,例如 10 个页面,页面大小为 25。在获得计数之前,我将计数基于顶部 ({PageSize} x {MaxPageCount} + 1) 或 251。要获得maxPageCount
,我们需要查看页码与# 要显示的预期页面数。 (即10)
int maxPageCount = (((page) / 10)+1) * 10;
int roughCountLimit = pageSize * maxPageCount + 1;
rowCount = set.Take(roughCountLimit).Count();
bool isRoughCount = rowCount == roughCountLimit;
var pagedData = set.Skip(page * pageSize).Take(pageSize).ToList();
对于第 1 到 10 页 这将返回最多 11 页。 IE
page #1 (0) / 10 = 0. (0+1)* 10 = 10.
page #2 (1) / 10 = 0. (0+1)* 10 = 10.
page #10 (9) / 10 = 0. (0+1)* 10 = 10.
这个想法是寻呼机将显示如下内容:
"1 2 3 4 5 6 7 8 9 10..." 而我们的页数将设置为查看isRoughCount
并显示:"250+" 而不是 "251" 如果isRoughCount
为True
。
如果并且当用户选择“...”来加载第 11 页时,则返回到maxPageCount
:
page #11 (10) / 10 = 1. (1+1)* 10 = 20.
这将导致roughCountLimit
变为 501。这将加载多达 21 页的记录。 如果数据库碰巧只返回 251 条记录,那么第 11 页仍会显示剩余的 1 条记录,并且由于 isRoughCount 为 false,因此行数将更新为显示“251”。 否则,页面计数将更新为显示“500+” 如果用户继续使用“...”浏览页面,粗略计数限制将继续增加。 这将使查询逐渐变慢,但对于最初的几组页面,查询检索计数将显着加快。
分页和搜索的关键是用户应该有工具来找到通常在第一页上的数据,或者可能是结果的前几页。 他们需要浏览 10 页结果的实际次数,更不用说超过 10 页的结果了,应该几乎永远不会。 (这表明您需要更好的搜索/过滤功能)同时,即使搜索非常好,处理非常大的数据集,用户通常也不会关心是 5000 行还是 500,000,000 行。 我们可以通过报告“至少”有 250 行来大大加快查询速度,然后在且仅在需要时对其进行扩展。 如果需要,页数可以显示为超链接以运行特定的完整计数查询,或者只是对特定的 504,231,188 行计数感到好奇。 这个(昂贵的)事实不需要成为每个查询的一部分。
不可能只访问数据库一次以获取对象和对象的数量。 如果要进行分页,则需要两个查询。 链接到类似问题
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.