theninclude用于在include基础上链式加载多级导航属性,必须作用于实体导航属性而非标量字段,且需避免与select混用导致静默失效;正确写法如.include(x => x.posts).theninclude(x => x.author).theninclude(x => x.profile)。

Include 不是“开了就能用”的开关,它只在查询执行前、且未被 LINQ 操作破坏的 IQueryable 上生效。写错位置、混用投影、路径不合法,都会导致它静默失效——你看到数据在,但背后是 N+1 查询。
Include 后接 Select 就大概率失效
这是最隐蔽也最常踩的坑:你以为写了 Include 就万事大吉,结果循环里访问 order.Customer.Name 还是触发额外 SQL。
-
Include只绑定到当前IQueryable实例;一旦调用Select(尤其是映射到匿名类型或 DTO),EF Core 会丢弃所有未显式出现在Select中的导航属性 - EF Core 6+ 会尝试自动 JOIN,但仅限于简单投影(如
Select(x => x)或Select(x => new { x.Id, x.Customer }));若写成Select(x => new { x.Id, x.Customer.Name }),Customer 实体本身没被选中,Include彻底作废 - 验证方法:开启日志
options.LogTo(Console.WriteLine),盯住生成的 SQL —— 是一条带 JOIN 的语句,还是先查主表、再为每个实例发一条子查询?
ThenInclude 只能点导航属性,不能点字段
报错 InvalidOperationException: The expression 'x => x.Address.City' is not a valid member access expression,基本就是你在 ThenInclude 里点了标量字段。
-
ThenInclude的参数必须是导航属性(即类型为另一个实体或集合的属性),比如Address可以,City(string)不行 - 如果
Address类里定义的是public string CityName { get; set; },那ThenInclude(a => a.CityName)非法;必须确保下一级仍是实体,例如Address.Province(类型为Province)才可继续ThenInclude - 别混用字符串路径和 Lambda:
Include("Customer").ThenInclude("Address")在 EF Core 6+ 已废弃,运行时可能静默失败或抛异常,统一用Include(x => x.Customer).ThenInclude(x => x.Address)
多级嵌套建议拆开写,别硬链
三层以上(如 Order → Customer → Address → Province)强行写成一行 Include(...).ThenInclude(...).ThenInclude(...) 容易断在某一级,调试困难。
- 拆成多个
Include+ThenInclude组合更稳定:Include(o => o.Customer).ThenInclude(c => c.Address)和Include(o => o.Customer).ThenInclude(c => c.Province)是合法且等效的 - 这样写的好处:每级可单独注释、可逐行注释掉测试、SQL 日志也更容易对应到哪一级漏了
- EF Core 会自动合并 JOIN,不会因为拆开就多发 SQL;性能无损,可维护性显著提升
AsSplitQuery 不是 Include 的平替
AsSplitQuery 解决的是「一对多 + 多对一混合导致的笛卡尔积膨胀」,不是所有 N+1 场景都适用。
- 它只在配合
Include时生效,单独调用无效 - 适用于主表少量记录 + 关联数据量大(如 1 个 Blog 带 1000 篇 Posts)的场景;若主表本身就有上万条,拆成多个查询反而加重数据库压力
- 不支持某些数据库提供程序(如 SQLite),且无法与
Distinct、GroupBy等操作共存
Include 加得少,运行时 N+1;加得多,SQL 膨胀、内存飙升、网络传输变慢。每次加之前,先问自己:这个导航属性接下来是否真会被读取?有没有更轻量的 Select 投影能替代?











