EF Core 3.1升级5.0后ThenInclude生成查询被忽略问题
问题背景
有两个基于.NET Core 3.1的项目,分别运行EF Core 3.1.17与EF Core 5.0.10,两个项目中采用相同写法使用Include/ThenInclude方法,均能正常运行。近期尝试将使用EF Core 3.1.17的项目升级至5.0.10版本,遇到了异常问题。
问题现象
在查询表达式中使用ThenInclude语句时,EF Core生成查询的过程中会忽略ThenInclude指定的关联实体,不会关联查询对应表数据。
使用的查询表达式如下:
var settings = await _dbRepository.GetRegionRoles(systemId) .Include(r => r.Region) .Include(sr => sr.SystemRole) .ThenInclude(ro => ro.Role) .Include(gs => gs.GroupSettings) .Where(w => w.GroupSettings.Any()) .ToListAsync(cancellationToken);
从跟踪日志可以看到,EF Core在编译查询表达式阶段能够识别所有ThenInclude语句(尝试在支持的位置添加更多ThenInclude语句,得到的结果一致),对应跟踪日志内容如下:
Compiling query expression: 'DbSet<RegionRole>() .Include(x => x.SystemRole) .Where(w => w.SystemRole.SystemId.Equals((int)__systemId_0)) .Include(r => r.Region) .Include(sr => sr.SystemRole) .ThenInclude(ro => ro.Role) .Include(gs => gs.GroupSettings) .Include(sr => sr.SystemRole) .ThenInclude(s => s.SystemCode) .Where(w => w.GroupSettings .Any())'
但最终生成的查询执行表达式中,并未包含ThenInclude关联的表JOIN逻辑,生成的查询执行表达式如下:
Generated query execution expression: 'queryContext => new SingleQueryingEnumerable<RegionRole>( (RelationalQueryContext)queryContext, RelationalCommandCache.SelectExpression( Projection Mapping: SELECT r.Id, r.CreatedByUser, r.CreatedDate //... and many other fields from each table FROM Main.RegionRole AS r INNER JOIN Main.SystemRole AS s ON r.SystemRoleId == s.Id INNER JOIN Main.Region AS r0 ON r.RegionId == r0.Id LEFT JOIN Main.GroupSettings AS g ON r.Id == g.RegionRoleId WHERE (s.SystemId == CAST(@__systemId_0) AS int)) && EXISTS ( Projection Mapping: SELECT 1 FROM Main.GroupSettings AS g0 WHERE (r.Id != NULL) && (r.Id == g0.RegionRoleId)) ORDER BY r.Id ASC, s.Id ASC, r0.Id ASC, g.Id ASC), Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, RegionRole>, K2Project.BE.DAL.Models.K2DbContext, False, False )'
从生成的表达式可以看出,ThenInclude语句关联的Role实体在最终查询中缺失,没有被关联查询。
所有实体均配置了正确的导航属性,通过Fluent API完成了实体映射,所有数据库表均已配置对应外键。
以下是SystemRole实体的定义示例:
public class SystemRole { public Guid Id { get; set; } public int RoleId { get; set; } #region Dependencies public virtual Role Role { get; set; } #endregion }
该实体对应的Fluent API配置如下:
public class SystemRoleEntityTypeConfiguration : IEntityTypeConfiguration<SystemRole> { public void Configure(EntityTypeBuilder<SystemRole> builder) { builder.ToTable(nameof(SystemRole), DbConst.MainSchemaName); builder.HasKey(p => p.Id); builder.Property(p => p.Id) .ValueGeneratedOnAdd() .HasDefaultValueSql("NEWSEQUENTIALID()"); builder.HasOne(p => p.Role) .WithMany() .HasForeignKey(f => f.RoleId) .IsRequired(true); } }
咨询问题
- EF Core 3.1.17与5.0.10版本中ThenInclude的运行逻辑存在什么差异?
- 为什么完全相同的查询表达式在3.1.17版本可正常运行,升级到5.0.10版本后就无法加载ThenInclude指定的关联实体?
- 是否是升级框架版本时遗漏了必要的配置或调整?
补充说明:另有一个从初始搭建就使用EF Core 5.0.10的项目,其中所有Include/ThenInclude语句均能正常运行,无上述问题。
问题原因与解决方案
这个问题本质是EF Core 5.0升级后查询管道的行为变更,问题出在自定义仓储的返回逻辑上:
- EF Core 3.1的查询管道逻辑比较直接,只要代码里写了Include/ThenInclude,不管查询链路里有没有其他包装逻辑,基本都会按配置生成对应JOIN。5.0版本做了性能优化,会自动裁剪它判定为“无效”的Include操作——如果
GetRegionRoles方法返回的不是EF Core原生追踪的IQueryable,比如方法内部提前做了枚举、投影,或者自定义的IQueryable包装丢失了EF的查询提供器标记,后面链式写的ThenInclude就会被当成无效逻辑直接忽略,根本不会生成对应JOIN。 - 原生搭建的5.0项目没有该问题,是因为所有查询都是直接从DbSet开始链式调用,全程都是EF原生的IQueryable实例,Include的标记不会在链路中丢失。
修复按以下步骤排查即可:
- 先检查
GetRegionRoles的实现,确认方法内部没有调用ToList()、AsEnumerable()这类会提前触发查询的方法,必须直接返回基于DbSet<RegionRole>构建的原生IQueryable,不能先查询出结果再转成IQueryable返回。 - 如果仓储层对IQueryable做了自定义包装,检查包装类有没有正确实现EF Core 5.0新增的
IAsyncQueryProvider接口,避免在传递表达式树的时候把Include/ThenInclude的方法节点意外筛除。 - 最快的验证方式:把这段查询直接改成从
DbContext.Set<RegionRole>()开始编写,跳过仓储层,如果能正常生成Role表的JOIN,就可以确认是仓储封装和EF Core 5.0不兼容的问题,调整仓储实现即可。 - 顺便检查DbContext配置,确认没有给Role实体配置会自动过滤的全局查询过滤器,排除过滤器导致的JOIN裁剪。
内容的提问来源于stack exchange,提问作者sir drake
相关产品推荐
相关产品推荐

