使用linq根据最后一条记录的id查询下一页,核心做法是先对数据按指定字段排序,跳过前n条记录(即上一页的数量),然后取m条记录作为当前页。如果需要根据“最后一条记录的id”进行游标分页(keyset pagination),则应筛选出id大于上一页最后一条记录id的数据,再按id排序并限制数量。这种方法比传统的skip/take更高效,因为它避免了在大数据量下skip带来的性能损耗,且能准确处理id不连续或重复的情况。

传统Skip与Take分页的性能瓶颈
Skip和Take是LINQ中最常用的分页方式,但在数据量大时效率较低。当执行Skip(10000).Take(10)时,数据库或内存需要遍历并丢弃前10000条记录,时间复杂度为O(N+K),其中N是跳过的数量,K是获取的数量。随着页码增加,性能急剧下降。

using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var data = new List<item>
{
new Item { Id = 1, Name = "A" },
new Item { Id = 2, Name = "B" },
new Item { Id = 3, Name = "C" },
new Item { Id = 4, Name = "D" },
new Item { Id = 5, Name = "E" }
};
int pageSize = 2;
int pageNumber = 2; // 第2页
// 传统分页:Skip(pageNumber * pageSize).Take(pageSize)
var pageData = data
.OrderBy(x => x.Id)
.Skip(pageNumber * pageSize)
.Take(pageSize)
.ToList();
foreach (var item in pageData)
{
Console.WriteLine($"Id: {item.Id}, Name: {item.Name}");
}
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}</item>
预期输出:
Id: 3, Name: C Id: 4, Name: D
基于最后一条记录ID的游标分页实现
游标分页(Cursor-based Pagination)通过记录上一页最后一条数据的唯一标识(如ID)来查询下一页。具体做法是:首先获取上一页最后一条记录的ID,然后在查询中筛选出ID大于该值的记录,并按ID升序排序,最后取指定数量的记录。这种方式的时间复杂度为O(K),与数据总量无关,适合大数据量场景。

using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var data = new List<item>
{
new Item { Id = 1, Name = "A" },
new Item { Id = 2, Name = "B" },
new Item { Id = 3, Name = "C" },
new Item { Id = 4, Name = "D" },
new Item { Id = 5, Name = "E" }
};
int pageSize = 2;
int lastId = 0; // 初始状态,假设从ID=0开始
// 游标分页:Where(x => x.Id > lastId).OrderBy(x => x.Id).Take(pageSize)
var nextPageData = data
.Where(x => x.Id > lastId)
.OrderBy(x => x.Id)
.Take(pageSize)
.ToList();
Console.WriteLine("第一页数据:");
foreach (var item in nextPageData)
{
Console.WriteLine($"Id: {item.Id}, Name: {item.Name}");
}
// 更新lastId为当前页最后一条记录的ID
if (nextPageData.Any())
{
lastId = nextPageData.Last().Id;
}
// 查询第二页
var secondPageData = data
.Where(x => x.Id > lastId)
.OrderBy(x => x.Id)
.Take(pageSize)
.ToList();
Console.WriteLine("\n第二页数据:");
foreach (var item in secondPageData)
{
Console.WriteLine($"Id: {item.Id}, Name: {item.Name}");
}
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}</item>
预期输出:
第一页数据: Id: 1, Name: A Id: 2, Name: B 第二页数据: Id: 3, Name: C Id: 4, Name: D
如何处理ID不连续或存在删除记录的情况
当数据中存在删除操作导致ID不连续时,游标分页依然有效,因为它只关心ID的大小关系,而不依赖连续的序列。例如,如果ID为2的记录被删除,查询ID大于1的记录会直接返回ID为3的记录,不会出现遗漏或重复。需要注意的是,如果ID不是唯一的(如复合主键或非自增ID),则需要结合其他字段(如创建时间)进行排序,以确保结果的一致性。此外,如果数据量极大,建议在数据库层面实现游标分页,而不是在内存中使用LINQ,以避免全表扫描的性能问题。











