You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core:Include预加载与直接上下文关联查询的性能对比及方案选择咨询

EF Core两种查询写法的性能对比与最优选择

首先先呼应官方提示的核心点:在单个查询中预加载集合导航属性确实可能引发性能问题,主要原因是会产生笛卡尔积,导致数据库返回大量重复的主实体数据,增加传输和客户端处理的开销。下面我们分别分析两种写法的特点:

写法一:使用Include+投影

var task = await context.Tasks
    .Include(x => x.TaskDependencies)
    .Select(x => new TaskBaseModel {
        Id = x.Id,
        Name = x.Name,
        Description = x.Description,
        TaskDependencies= x.TaskDependencies.ToArray()
    })
    .SingleOrDefaultAsync(x => x.Id == _id);

工作原理与性能表现

  • EF Core会生成单个JOIN查询,将Tasks表和TaskDependencies表关联起来,返回的结果是笛卡尔积形式:比如一个Task有10个依赖,就会返回10条包含相同Task信息、不同依赖信息的记录。
  • 之后EF Core会在客户端将这些重复的Task数据合并,把依赖项分组到对应的Task对象中。
  • 优势:仅发起1次数据库请求,减少了网络往返次数,在依赖项数量极少时,性能表现尚可。
  • 劣势:当依赖项数量较多时,会产生大量重复的Task字段数据,增加数据库传输的带宽消耗,同时客户端需要额外的内存和CPU来处理分组逻辑,整体开销会显著上升。

写法二:使用关联子查询

var task = await context.Tasks
    .Select(x => new TaskBaseModel {
        Id = x.Id,
        Name = x.Name,
        Description = x.Description,
        TaskDependencies= context.TaskDependencies
            .Where(y => y.TaskId == x.Id).ToArray()
    })
    .SingleOrDefaultAsync(x => x.Id == _id);

工作原理与性能表现

  • 这种写法会生成2次独立的数据库请求:第一次查询目标Task的基础信息,第二次根据TaskId查询对应的所有TaskDependencies。
  • 优势:不会产生笛卡尔积,返回的数据都是必要的,没有重复的Task字段,数据库传输的数据量更小,客户端也无需处理分组逻辑,在依赖项较多时性能更优。
  • 劣势:多了一次网络往返,但因为是查询单个Task(SingleOrDefault),所以仅增加1次请求,对整体性能影响不大;但如果是批量查询多个Task时,这种写法容易引发N+1查询问题(每个Task都发起一次依赖查询),需要特别注意。

官方推荐的最优方案:拆分查询(Split Query)

针对集合导航属性预加载的性能问题,EF Core官方推荐使用拆分查询,也就是在Include后添加.AsSplitQuery(),它能自动将关联查询拆分为多个独立的查询,既避免笛卡尔积,又保持代码的简洁性:

var task = await context.Tasks
    .Include(x => x.TaskDependencies)
    .AsSplitQuery()
    .Select(x => new TaskBaseModel {
        Id = x.Id,
        Name = x.Name,
        Description = x.Description,
        TaskDependencies= x.TaskDependencies.ToArray()
    })
    .SingleOrDefaultAsync(x => x.Id == _id);

为什么推荐拆分查询?

  • 它会自动生成2次查询(和写法二类似):一次查Task,一次查对应的TaskDependencies,避免了笛卡尔积的开销。
  • 代码写法更符合EF Core的惯用模式,通过Include明确表达导航属性的关联关系,可读性和可维护性比写法二更优。
  • 针对批量查询场景,拆分查询会生成2次批量查询(一次查所有Task,一次查所有对应的依赖),不会出现N+1问题,性能远优于写法二。

技术建议总结

  • 如果是查询单个Task:拆分查询和写法二性能差异不大,但拆分查询代码更简洁,优先选择。
  • 如果是批量查询多个Task:绝对避免写法二(容易N+1),优先使用拆分查询。
  • 尽量避免写法一的单查询预加载集合导航属性,尤其是当集合元素数量较多时,笛卡尔积的开销会成为性能瓶颈。
  • 可以开启EF Core的SQL日志,查看每种写法生成的SQL语句,直观理解查询的执行逻辑,帮助你做出更合适的选择。

内容的提问来源于stack exchange,提问作者MarchalPT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:59:06