EF Core 5.0.9同时使用Filtered Include与Select过滤失效问题咨询
问题原因
这是EF Core的设计特性而非Bug:当查询中使用了自定义投影(Select)时,所有之前声明的Include(包括过滤型Filtered Include)都会被EF Core自动忽略。
Include的设计作用范围仅局限于返回实体类型的查询:当你直接查询User实体时,EF Core会按照Include的规则加载关联数据并应用过滤;但当你指定了Select投影到DTO时,EF Core会完全按照你投影内的逻辑生成SQL,不会复用Include里定义的过滤规则,所以你看到生成的SQL里所有EventUsers的子查询都没有带上你写的!eu.TOROLT && eu.EventId == eventId条件。
而你拆分执行时生效的原因是:第一步ToList是直接返回User实体,此时Include生效,过滤后的EventUsers已经被加载到上下文内存中,第二步的Select是内存中的LINQ to Objects操作,自然会使用已经过滤好的关联集合,缺点是会查询User和EventUser的全量字段,额外产生性能开销。
解决方案
要同时满足过滤关联数据、仅查询需要字段的需求,不需要依赖Include,直接把过滤逻辑写到投影逻辑中即可,推荐用匿名类做中间过渡避免重复写过滤条件,示例代码如下:
return await _dbContext.User .Where(u => !u.TOROLT) .Where(u => ids.Contains(u.Id)) .Select(u => new { u.Id, u.FirstName, u.LastName, // 直接在投影中定义过滤规则,EF Core会生成带过滤的子查询 TargetEventUser = u.EventUsers.First(eu => !eu.TOROLT && eu.EventId == eventId) }) .Select(temp => new UserDropDownDtoWithInviteData { Id = temp.Id, FirstName = temp.FirstName, LastName = temp.LastName, EventUserId = temp.TargetEventUser.Id, IsCelebrated = temp.TargetEventUser.IsCelebrated, IsEventAdmin = temp.TargetEventUser.IsEventAdmin, IsInviteAccepted = temp.TargetEventUser.IsInviteAccepted, IsInvited = temp.TargetEventUser.IsInvited, }) .ToListAsync();
这种写法生成的SQL会自动为EventUser的子查询加上你需要的过滤条件,同时只会查询DTO需要的字段,不会产生多余的性能开销,完全兼容EF Core 5.0.9版本。如果存在用户没有匹配的EventUser的场景,可以把First换成FirstOrDefault,之后对TargetEventUser做空值处理,避免运行时异常。
内容的提问来源于stack exchange,提问作者Máté Izsóf

