C# LINQ实现GroupBy后按条件选择记录替代foreach循环
LINQ 关联集合扁平化查询改写方案
你自行改写的LINQ版本存在几个明显问题,会导致运行报错或者性能损耗:
- 空引用风险:关联联系人集合的默认返回值设为
null,后续直接调用x.Contact.Any()时,只要Contact为null就会抛出空引用异常,和原始foreach逻辑的空值处理规则不匹配。 - 去重逻辑冗余:用
GroupBy取第一条的方式实现联系人去重完全没有必要,直接用Distinct()即可,EF/EF Core可以直接将其翻译为SQL的DISTINCT关键字,在数据库侧完成去重,性能远高于内存分组。 - 语法错误:Key1赋值行多写了一个多余的逗号,会直接导致编译失败。
- 查询执行时机不合理:投影完匿名类型就立刻调用
ToList(),会把所有中间结果全量拉到内存再做扁平化处理,完全可以把数据过滤、投影的逻辑放在数据库侧执行,减少数据传输量和内存占用。
下面是完全对齐原始foreach逻辑、无显式循环的优化后实现:
return _context.MainRecordTable .Where(h => h.id == Id) .Select(lev => new { // 空链路判断后取有效联系人,用空集合替代null作为默认值,避免后续空引用 Contacts = (lev.basetable2 != null && lev.basetable2.basetable3 != null && lev.basetable2.basetable3.basetable6 != null && lev.basetable2.basetable3.basetable6.Any(h=>h.contact != null) ? lev.basetable2.basetable3.basetable6.Where(h=>h.contact != null).Select(h=>h.contact).Distinct() : Enumerable.Empty<Contact>()), Key1 = lev.basetable5 != null ? lev.basetable5.Id : null, Key2 = lev.basetable2 != null && lev.basetable2.basetable3 != null && lev.basetable2.basetable3.basetable4 != null ? lev.basetable2.basetable3.basetable4.id : null }) .Distinct() // 用SelectMany一步完成嵌套循环的扁平化逻辑 .SelectMany(row => // 无有效联系人时返回单条仅带Key1、Key2的记录 !row.Contacts.Any() ? new List<ResultModel> { new ResultModel { Key1 = row.Key1, Key2 = row.Key2 } } // 有有效联系人时,每个联系人生成一条带ContactKey的记录 : row.Contacts.Select(contact => new ResultModel { Key1 = row.Key1, Key2 = row.Key2, ContactKey = contact.id }) ) .ToList();
补充说明:
- 如果项目使用C# 6及以上版本,可以用
?.空条件运算符简化多层null判断的写法,EF Core 2.1+版本可以正常将该语法翻译为对应的SQL空判断逻辑,不会有额外性能损耗。- 关联集合默认返回空集合而非null是LINQ查询的通用最佳实践,可以避免绝大多数集合遍历场景下的空引用异常,不需要在后续逻辑中重复做null判断。
- EF Core 3.0及以上版本可以正常翻译上述SelectMany逻辑为标准SQL(通过LEFT JOIN + 判别列的方式实现),不需要把中间数据拉到内存处理,性能远高于手写foreach循环的内存拼接实现。
- 如果确实需要在内存中处理中间逻辑,只需要在Distinct()后调用
AsEnumerable()即可,不要过早执行查询拉取全量数据。
内容的提问来源于stack exchange,提问作者Kamran Shahid
相关产品推荐
相关产品推荐

