EF Core 分页查询

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


EF Core 分页查询

分页的核心是结合 Skip 和 Take 方法,并始终通过 IQueryable 保持查询在数据库端执行。


一、代码示例

基础分页实现

/// <summary>
/// 分页方法
/// </summary>
/// <param name="pageIndex">页码</param>
/// <param name="pageSize">每一页包含的数据条数</param>
static async Task<PageResult<Article>> GetDataByPageIndex(int pageIndex,int pageSize)
{
    if (pageIndex < 1) throw new ArgumentException("页码必须大于0");
    if (pageSize < 1) throw new ArgumentException("每页数量必须大于0");
    using (MyDbContext myDbContext =new MyDbContext())
    {
        IQueryable<Article> articles = myDbContext.Articles.OrderBy(a=>a.Id);// Where(a => !a.Title.Contains("标题"));
        // 异步获取总记录数
        long totalCount = await articles.LongCountAsync();
        // 计算跳过的记录数
        var skip = (pageIndex - 1) * pageSize;

        var items = await articles.Skip(skip).Take(pageSize).ToListAsync() ;
        //foreach (var item in items)
        //{
        //    Console.WriteLine(item.Title);
        //}
        //long count = articles.LongCount();
        //long pageCount =(long)Math.Ceiling(count*1.0/pageSize);
        //Console.WriteLine($"总页数:{pageCount}");

        return new PageResult<Article>
        {
            Items = items,
            TotalCount=totalCount,
            PageIndex=pageIndex,
            PageSize=pageSize
        };
    }
}
/// <summary>
/// 分页结果封装类
/// </summary>
/// <typeparam name="T"></typeparam>
public class PageResult<T>
{
    public List<T> Items { get; set; }
    public long TotalCount { get; set; }
    public int PageIndex { get; set; }
    public int PageSize { get; set; }
    public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
}

生成的SQL(以 SQL Server 为例)

-- 获取总数
SELECT COUNT_BIG(*)
      FROM [T_Articles] AS [t]
      
-- 获取分页数据
 SELECT [t].[Id], [t].[Content], [t].[Title]
      FROM [T_Articles] AS [t]
      ORDER BY [t].[Id]
      OFFSET @__p_0 ROWS FETCH NEXT @__p_0 ROWS ONLY
  1. Skip(3).Take(8)最好显示指定排序规则
  2. 需要知道满足条件的数据的总条数:用IQueryable的复用
  3. LongCount和Count
  4. 页数:long pageCount = (long)Math.Ceiling(count*1.0/pageSize);

二、关键注意事项

1)必须指定排序

  1. 分页查询必须有明确的排序规则(如 OrderBy),否则数据库返回顺序不稳定,导致分页数据重复或丢失。
  2. 推荐:使用唯一键(如 Id)或业务字段组合排序。

2)保持IQueryable

  1. 确保分页逻辑在 IQueryable 上操作,避免过早调用 ToList()AsEnumerable(),否则会导致全表加载到内存再分页。
  2. 错误示例:
    // 错误!全表加载到内存后分页
    var data = dbContext.Persons.ToList().Skip(10).Take(5);
    

3)异步优化

  1. 使用 CountAsync()ToListAsync() 避免阻塞线程,提升并发性能。

三、性能技巧优化

1)键集分页(Keyset Pagination)

  1. 适用于超大数据集,避免 OFFSET 的性能问题。通过记录上一页的最后一个键值实现。
// 假设按 Id 排序,获取下一页
var lastId = 100; // 上一页最后一条记录的 Id
var nextPage = await dbContext.Persons
    .Where(p => p.Id > lastId)
    .OrderBy(p => p.Id)
    .Take(pageSize)
    .ToListAsync();

优点

  1. 无需计算 TotalCount。
  2. 查询速度稳定,不受页码影响。

缺点

  1. 不支持随机跳页(如直接跳转到第5页)。
  2. 需要客户端维护排序键值。

2)仅查询必要字段

  1. 使用 Select 投影减少数据传输量

    var items = await query
        .Select(p => new PersonDto 
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price
        })
        .Skip(skip)
        .Take(pageSize)
        .ToListAsync();
    

四、常见问题处理

1)参数验证

  1. 检查 pageIndex 和 pageSize 的合法性(如最小值)。
  2. 处理超出总页数的情况,返回空列表或错误信息。

2)动态排序

  1. 允许用户自定义排序字段
IQueryable<person> query = dbContext.Persons;

if (sortBy == "price")
    query = query.OrderBy(p => p.Price);
else 
    query = query.OrderBy(p => p.Id);

// 后续分页逻辑相同

3)多条件筛选

  1. 动态组合查询条件
if (!string.IsNullOrEmpty(name))
    query = query.Where(p => p.Name== name);

if (minHeight.HasValue)
    query = query.Where(p => p.Height>= minHeight.Value);

总结

  1. 优先选择键集分页:处理超大数据(如日志、流水记录)。
  2. 常规分页:使用 Skip/Take 并确保排序和 IQueryable 优化。
方案适用场景优点缺点
OFFSET 分页需要随机跳页、中小型数据集实现简单,支持任意页码跳转大数据量时性能下降
键集分页超大数据集、连续分页(如无限滚动)性能稳定,无需计算总数无法跳转任意页,需维护排序键
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值