分页和实体框架

Posted

技术标签:

【中文标题】分页和实体框架【英文标题】:Pagination and Entity Framework 【发布时间】:2022-01-01 12:10:03 【问题描述】:

在我的移动应用程序中,我尝试从我的 SQL Server 数据库的表中检索数据。我正在使用 EF,并尝试使用分页来获得更好的性能。我需要从表的最后一个元素中检索数据。所以如果表有 20 行,我需要,对于第 0 页,ID 为 20、19、18、17、16,然后对于第 1 页,ID 为 15、14、13、12、11 等等......

问题是这样的:如果用户“A”正在从表中下载数据,用户“B”添加行怎么办?如果用户“A”获得第 0 页(即 ID 20、19、18、17、16),并且用户“B”在同一时刻添加行(即 ID 21),则使用经典查询,用户“A”为第 1 页将获得 ID 16, 15, 14, 13, 12... 所以另一次 ID 16

我的代码很简单:

int RecordsForPagination = 5; 
var list = _context.NameTable
                   .Where(my_condition)
                   .OrderByDescending(my_condition_for ordering)
                   .Skip (RecordsForPagination * Page)
                   .Take (RecordsForPagination)
                   .ToList();

当然Page 是来自前端的int。

我该如何解决这个问题?

我找到了一个解决方案,但我不知道它是否完美。我可以使用

.SkipWhile(x => x.ID >= LastID) 

改为

.Skip (RecordsForPagination * Page)

当然LastID 总是从前端发送。

你认为这段代码的性能总是很好吗?有没有更好的解决方案?

【问题讨论】:

分页是解决用户可能查询的数据多于一次合理显示的问题的解决方案。正确排序数据后,预计用户将主要查看第 1 页。(俗话说,“隐藏尸体的最佳位置是 Google 搜索结果的第 2 页。”)如果你真的必须将结果显示为“时间点”,然后确保使用行记录 CreatedAt 并使用 .Where(x => x.CreatedAt <= firstRunAt) 捕获初始搜索,其中 firstRunAt 在搜索开始时捕获。但这确实是寻找问题的解决方案。 【参考方案1】:

性能影响在很大程度上取决于您的 SQL 索引实现和 order by 子句。但这与其说是关于性能的问题,不如说是关于获得预期结果的问题。

Stack Overflow 是一个很好的示例,其中存在大量活动,当您到达任何页面的末尾时,下一页可能包含您刚刚查看的页面中的记录,因为基础记录集已更改(更多帖子有已添加)

我提出这一点是因为在实时系统中它被普遍接受,并且在某些情况下是预期的行为。作为开发人员,我们很欣赏尝试维护单个结果集所增加的开销,并认识到在迭代页面时尝试防止看起来像 重复 的价值通常要低得多。

通常向用户解释为什么会发生这种情况就足够了,在很多情况下他们会接受它

如果维护原始结果集中的位置对您很重要,那么您应该使用 Where 子句限制查询,但您需要检索原始查询中的 Id 或时间戳。在您的情况下,您尝试使用 LastID,但要获取最后一个 ID,需要单独查询它自己,因为 orderby 子句会影响它。

您不能真正为此使用.SkipWhile(x => x.ID >= LastID),因为skip 是一个受顺序影响的顺序过程,并且在表达式计算为false 的第一个实例中不参与,因此如果您的订单不是基于Id,您的跳过可能会导致根本不跳过任何记录。

int RecordsForPagination = 5; 
int? MaxId = null;
...
var query = _context.NameTable.Where(my_condition);
// We need the Id to constraint the original search
if (!MaxId.HasValue)
    MaxId = query.Max(x => x.ID);

var list = query.Where(x => x.ID <= MaxId)
                .OrderByDescending(my_condition_for ordering)
                .Skip(RecordsForPagination * Page)
                .Take(RecordsForPagination);
                .ToList();

按时间点过滤通常更简单,因为从客户端知道这一点无需往返数据库,但根据实施情况,日期过滤效率可能较低。

【讨论】:

我的意图不是从 DB 中检索 LastID(因为,正如您所说,这会受到 DB 本身的影响)。我的意图是: - 前端检索第 0 页,结果是 ID 20、19、18、17、16 - 前端检索第 1 页并传递给后端两个参数:第 1 页和上一页的 LastID(在本例中为 LastID = 16) -> 所以结果将是 ID 15、14、13、12、11...等等。我的英语不完美……我们说的是同一件事吗? @user1106897 Chris 指的技术叫做Keyset Pagination,一般比你说的按行分页好很多 它非常也是一个性能问题:按行分页效率非常低,因为每次都需要读取所有先前的行。而如果有支持索引,则按键(或本例中的时间)分页效率很高 Charlie face 非常感谢您提供的链接,非常感谢。我会尝试使用建议 感谢@Charlieface,性能评论更应该是“忘记性能,如果您更改订单,SkipWhile(by id) 将无法解决问题” 很棒的链接!

以上是关于分页和实体框架的主要内容,如果未能解决你的问题,请参考以下文章

物理分页和逻辑分页

学习MVC框架,处理分页和删除分页转跳的问题

分页和图片上传

逻辑分页和物理分页

UICollectionView、分页和 clipsToBounds [重复]

自动分页和排序和过滤到剃须刀页面脚手架