EF Core 3.1中Include生成无过滤子查询全表扫描如何优化?
问题原因
你遇到的是EF Core 3.1版本的查询翻译器已知缺陷:处理一对多关联的Include+ThenInclude时,为了避免关联查询产生笛卡尔积,会将集合关联封装为独立子查询,但不会把主表的过滤条件下推到子查询内部,最终导致子查询全表扫描后再做关联,性能极差。这个问题在EF Core 5.0及以上版本已经被官方修复。
解决方案
方案1:优先升级EF Core版本
直接升级到EF Core 5.0或更高版本,不需要修改现有查询代码,翻译器会自动将主表的MeetingId == 1过滤条件下推到每个集合子查询中,不会出现全表扫描问题。
方案2:使用拆分查询(EF Core 3.1支持)
如果暂时不能升级大版本,可以在查询末尾加上AsSplitQuery()方法,让EF Core将多个关联加载拆分为多条独立的SQL语句执行,每条SQL都会携带过滤条件,避免全表扫描:
Meetings .Include(a => a.Document) .Include(a => a.Plan) .Include(a => a.User) .Include(a => a.Topics).ThenInclude(e => e.Extra) .Include(a => a.Components).ThenInclude(g => g.Extra) .Include(a => a.Recipients).ThenInclude(i => i.Info) .AsSplitQuery() .Single(a => a.MeetingId == 1)
方案3:使用显式加载
如果拆分查询不符合你的业务需求,可以改为先查主表实体,再手动加载关联集合,生成的SQL都会自动携带主表ID过滤条件:
// 先加载主表和一对一关联 var meeting = Meetings .Include(a => a.Document) .Include(a => a.Plan) .Include(a => a.User) .Single(a => a.MeetingId == 1); // 手动加载每个一对多关联 dbContext.Entry(meeting) .Collection(a => a.Topics) .Query() .Include(e => e.Extra) .Load(); dbContext.Entry(meeting) .Collection(a => a.Components) .Query() .Include(g => g.Extra) .Load(); dbContext.Entry(meeting) .Collection(a => a.Recipients) .Query() .Include(i => i.Info) .Load();
方案4:使用投影查询
如果你不需要返回完整的Meeting实体,只需要用到部分字段,建议直接用Select做投影查询,不需要写Include,生成的SQL天然更精简,不会出现全表扫描问题:
var meetingData = Meetings .Where(a => a.MeetingId == 1) .Select(a => new { a.MeetingId, a.Title, a.Document, a.Plan, a.User, Topics = a.Topics.Select(t => new { t, t.Extra }).ToList(), Components = a.Components.Select(c => new { c, c.Extra }).ToList(), Recipients = a.Recipients.Select(r => new { r, r.Info }).ToList() }) .Single();
内容的提问来源于stack exchange,提问作者Antediluvian
相关产品推荐
相关产品推荐

