欢迎光临
我们一直在努力

【EF Core】 5. IQueryable、分页查询

本系列专栏基于杨中科老师的《ASP.NET Core技术内幕与项目实战》,本人记录梳理的学习笔记,有部分的增补和省略。更全面系统的讲解,请看杨老师的视频课:【.NET教程,.Net Core视频教程,杨中科主讲】。

一、IQueryable 

IQueryable 是 EF Core 中数据库查询的核心接口,继承自 IEnumerable,专为远程数据源查询设计,是实现高效数据访问的关键。

1. IQueryable 与 IEnumerable 的关系

两者都是可枚举集合接口,但数据处理位置、执行机制有区别:

  • IEnumerable(内存集合):执行客户端评估。先把数据库所有数据加载到本地内存,再在内存中执行过滤、排序等操作。(缺点:数据量大时会造成内存溢出、网络传输浪费。)
  • IQueryable(数据库查询):执行服务器端评估。EF Core 会将查询表达式翻译为对应的 SQL 语句,直接在数据库中执行筛选后,仅返回需要的数据。(优点:性能极高,是 EF Core 操作数据库的标准用法。)

2. 核心特点

(1)延迟执行(懒加载)

这是 IQueryable 最重要的特性:

  • 非终结方法:Where()、OrderBy()、Skip()、Take()、Include()、GroupBy() → 不执行数据库查询,仅构建查询表达式树。
  • 终结方法:ToList()/ToListAsync()、ToArray()、FirstOrDefault()、Count()、foreach 遍历 → 立即执行 SQL 查询,获取数据。

执行流程:构建查询规则 → 调用终结方法 → 生成 SQL → 数据库执行 → 返回结果。

(2)查询可复用

IQueryable 本质是待执行的查询逻辑,而非已加载的数据,因此可以多次复用、拼接条件:

// 基础查询:只查询状态为启用的书籍
var query = ctx.Books.Where(b => b.IsEnabled == true);

// 复用基础查询:拼接价格筛选
var cheapBooks = query.Where(b => b.Price < 50);
// 复用基础查询:拼接排序
var orderedBooks = query.OrderBy(b => b.PublishTime);

3. 底层工作原理

(1)数据读取底层依赖:IQueryable 默认使用 DataReader 流式读取数据;

(2)DataReader VS DataTable(ToList/ToArray):

方式加载方式内存占用数据库连接占用适用场景
DataReader 分批流式读取 极小 长(持续占用) 大数据量遍历、无需缓存全部数据
ToList/ToArray 一次性加载全部到内存 较大 短(执行即释放) 数据量小、需要复用数据、上下文销毁后使用

(3)必用 ToList/ToArray 的场景:

  • DbContext 即将被销毁,仍需要使用查询结果;
  • 多个 IQueryable 嵌套查询,避免上下文冲突;
  • 长时间处理数据,防止霸占数据库连接。

4. 异步遍历 IQueryable(推荐)

异步编程是 ASP.NET Core 高性能的关键,使用方法:

// 异步查询:不会阻塞主线程
await foreach (var article in ctx.Articles.AsAsyncEnumerable())
{
Console.WriteLine(article.Title);
}

// 等价写法:先转为集合再遍历
var articles = await ctx.Articles.ToListAsync();
foreach (var a in articles)
{
Console.WriteLine(a.Title);
}

注意:异步方法必须搭配 await 使用,方法返回值为 Task/ValueTask。

二、EF Core 分页查询

分页查询是 Web 开发必备功能,核心逻辑:统计总数据量 → 计算总页数 → 跳过指定行数 → 获取当前页数据。

1. 核心逻辑

  • 用 Count()/LongCount() 查询数据总条数;
  • 定义 pageIndex(页码,从 1 开始)、pageSize(每页条数);
  • 公式计算:总页数 = 向上取整(总条数 / 每页条数);
  • 用 Skip((pageIndex-1)*pageSize).Take(pageSize) 实现分页。
  • 2. 示例

    /// <summary>
    /// 分页查询书籍
    /// </summary>
    /// <param name="pageIndex">页码(从1开始)</param>
    /// <param name="pageSize">每页条数</param>
    async Task OutputPageAsync(int pageIndex, int pageSize)
    {
    // 入参校验:防止页码/页数为负数
    if (pageIndex < 1) pageIndex = 1;
    if (pageSize < 1) pageSize = 10;

    using (var ctx = new MyDbContext())
    {
    // 1. 获取基础 IQueryable 查询(未执行数据库)
    IQueryable<Book> booksQuery = ctx.Books;

    // 2. 异步统计总条数(执行SQL:SELECT COUNT(*) FROM Books)
    long totalCount = await booksQuery.LongCountAsync();
    Console.WriteLine($"数据总条数:{totalCount}");

    // 3. 计算总页数(Math.Ceiling:向上取整)
    long totalPage = (long)Math.Ceiling(totalCount * 1.0 / pageSize);
    Console.WriteLine($"总页数:{totalPage}");

    // 4. 分页查询(执行SQL:SELECT * FROM Books LIMIT … OFFSET …)
    var pageData = await booksQuery
    .Skip((pageIndex – 1) * pageSize) // 跳过前N页数据
    .Take(pageSize) // 取当前页数据
    .ToListAsync(); // 异步执行查询

    // 5. 遍历输出
    if (pageData.Any())
    {
    foreach (var item in pageData)
    {
    Console.WriteLine($"书名:{item.Title},价格:{item.Price}");
    }
    }
    else
    {
    Console.WriteLine("当前页无数据");
    }
    }
    }

    // 调用示例
    await OutputPageAsync(1, 10); // 第1页,每页10条
    await OutputPageAsync(3, 5); // 第3页,每页5条

    3. 优化建议

    • 页码从 1 开始:符合用户使用习惯,避免 pageIndex=0 异常;
    • 限制最大每页条数:防止恶意请求(如 pageSize=10000)导致性能问题;
    • 大数据量分页:优先使用主键分页替代 Skip(Skip 越深性能越差);
    • 搭配排序:分页必须配合 OrderBy() 使用,否则数据顺序不稳定(EF Core 强制要求)。

    总结

  • IQueryable 是 EF Core 高效查询的核心,实现服务器端 SQL 执行和延迟加载,性能远优于 IEnumerable;
  • 核心规则:非终结方法拼查询,终结方法执查询,大数据量优先用异步方法;
  • 分页查询 固定公式:Skip((页码-1)*每页条数).Take(每页条数),配合总条数计算实现完整分页;
  • 实战中优先使用异步编程,做好入参校验,提升代码健壮性。
  • 赞(0)
    未经允许不得转载:171主机测评 » 【EF Core】 5. IQueryable、分页查询
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址