LINQ分组查询投影时抛出EmptyProjectionMember键不存在异常
KeyNotFoundException: EmptyProjectionMember 异常原因及解决方案
异常原因
这个异常是Entity Framework(EF)在处理GroupBy结果直接投影时的查询转换bug,和你标记为virtual的二进制字段直接相关:
virtual修饰的字段会被EF默认识别为延迟加载属性- 当你在GroupBy的结果上直接执行
Select投影时,EF的查询转换器在生成SQL的过程中,会错误地尝试关联这些未被包含在投影中的延迟加载属性对应的内部投影成员,导致字典查找失败,抛出KeyNotFoundException。
验证依据
你做的几个测试完全符合这个bug的表现:
- 移除
Select后,EF会加载实体的所有字段(包括virtual属性),不会触发投影成员的查找逻辑,因此正常运行 - 投影空对象或固定值时,EF不需要处理实体字段的映射,也不会触发这个bug
- 提前用
ToList()将GroupBy结果加载到内存后再投影,EF不再参与SQL转换,完全在内存中处理,因此避免了bug
解决方案
方案1:内存中投影(你已发现的可行方案)
将GroupBy后的结果先加载到内存,再执行投影,跳过EF的查询转换逻辑:
var eventImages = (context.EventImages.AsNoTracking() .Where(ei => uniqueEventIds.Contains(ei.EventItemId)) .GroupBy(img => img.EventItemId, (key, g) => g.OrderBy(img => img.EventImageId).FirstOrDefault())) .ToList() // 加载到内存 .Select(img => new { img.EventImageId, img.EventItemId, img.EventImageFileExtension, }) .ToList();
方案2:用RowNumber替代GroupBy(数据库端完成投影,效率更高)
换一种方式实现“每个EventItemId取第一条记录”的逻辑,避免GroupBy带来的查询转换冲突,同时直接在数据库端完成投影:
var eventImages = context.EventImages.AsNoTracking() .Where(ei => uniqueEventIds.Contains(ei.EventItemId)) .Select(img => new { img.EventImageId, img.EventItemId, img.EventImageFileExtension, Rank = EF.Functions.RowNumber() .Over(PartitionBy(img.EventItemId).OrderBy(img.EventImageId)) }) .Where(x => x.Rank == 1) .Select(x => new { x.EventImageId, x.EventItemId, x.EventImageFileExtension }) .ToList();
方案3:移除virtual修饰(如果不需要延迟加载)
如果这两个二进制字段不需要延迟加载,直接去掉virtual修饰,EF就不会将它们视为延迟加载属性,查询转换时就不会触发这个bug:
// 修改实体类 public class EventImage { public int EventImageId { get; set; } public int EventItemId { get; set; } public string EventImageFileExtension { get; set; } // 移除virtual修饰 public byte[] EventImageFullSizeBytes { get; set; } public byte[] EventImageThumbnailBytes { get; set; } }
内容的提问来源于stack exchange,提问作者EvilDr
相关产品推荐
相关产品推荐

