升级EF Core 8后LINQ查询执行极慢问题排查
问题场景
原LINQ查询在EF Core 6/.NET 6环境下执行极快,但升级到.NET 8+EF Core 8后,执行耗时飙升至17秒。然而生成的底层SQL在SSMS中执行仅需不到1秒。
原查询代码:
return _db.LocationPolygons .Include(x => x.LocationPoints.OrderBy(y => y.SortOrder)) .Where(x => x.LocationId == locationId && x.DeletedDateTime == null) .ToList();
生成的SQL:
DECLARE @__locationId_0 int = 11967; SELECT [l].[LocationPolygonId], [l].[CentroidLat], [l].[CentroidLong], [l]. [DeletedDateTime], [l].[Description], [l].[Editable], [l].[LocationId], [l0]. [LocationPointId], [l0].[Latitude], [l0].[LocationPolygonId], [l0].[Longitude], [l0]. [SortOrder] FROM [LocationPolygon] AS [l] LEFT JOIN [LocationPoint] AS [l0] ON [l].[LocationPolygonId] = [l0].[LocationPolygonId] WHERE [l].[LocationId] = @__locationId_0 AND [l].[DeletedDateTime] IS NULL ORDER BY [l].[LocationPolygonId], [l0].[SortOrder]
环境信息:
- 两张表均使用int类型主键
- LocationPoint表有
LocationPolygonId + SortOrder联合索引 - LocationPolygon表有
LocationId + DeletedDateTime联合索引 - LocationPoint与LocationPolygon间外键约束配置正确
- 查询返回1条LocationPolygon记录,关联44000条LocationPoints子实体
尝试过.AsSplitQuery()但性能提升不明显;手动改写为DTO投影后,查询耗时降至1秒内。
手动DTO查询代码:
var results = from polygons in _db.LocationPolygons where polygons.LocationId == locationId && polygons.DeletedDateTime == null select new LocationPolygonDto { LocationPolygonId = polygons.LocationPolygonId, CentroidLat = polygons.CentroidLat, CentroidLong = polygons.CentroidLong, Description = polygons.Description, LocationId = locationId, Editable = polygons.Editable, LocationPoints = _db.LocationPoints.Where(x => x.LocationPolygonId == polygons.LocationPolygonId).Select(x => new PointDto { Latitude = x.Latitude, Longitude = x.Longitude }).ToList() }; return results.ToList();
核心原因分析
实体跟踪与实例化开销:
Include方式会创建完整的实体对象,EF Core需要为每个实体维护状态跟踪(如变更检测),同时要处理导航属性的关联关系(将44000个子实体绑定到主实体上)。而DTO投影只生成轻量级的DTO对象,无需状态跟踪,内存开销和处理逻辑大幅减少。Join结果的重复数据合并:
生成的Join SQL会返回44000行包含重复主实体数据的结果集,EF Core需要在内存中把这些重复的主实体合并成单个实例,同时逐一关联对应的子实体。这个合并逻辑在EF Core 8中存在性能退化,相比EF Core 6的处理效率明显降低。导航属性排序的额外处理:
虽然SQL中已经按SortOrder排序,但EF Core在处理带排序的导航属性时,可能在内存中再次执行排序或额外的集合处理逻辑,进一步增加了开销。
解决方案
1. 优先使用DTO投影(已验证有效)
直接在查询中构造DTO是最优方案,既避免了EF的实体跟踪开销,又减少了不必要的对象实例化和关系维护逻辑,尤其适合子实体数量庞大的场景。
2. 关闭实体跟踪(若需返回实体)
如果必须返回实体而非DTO,可添加.AsNoTracking()关闭状态跟踪,减少内存开销:
return _db.LocationPolygons .Include(x => x.LocationPoints.OrderBy(y => y.SortOrder)) .Where(x => x.LocationId == locationId && x.DeletedDateTime == null) .AsNoTracking() .ToList();
3. 升级EF Core补丁版本
检查是否为EF Core 8的已知性能问题,尝试升级到最新的EF Core 8补丁版本,部分性能退化问题可能已被官方修复。
内容的提问来源于stack exchange,提问作者Aaron

