使用Contains对比双列表时的LINQ性能优化问询及方案探讨
我需要对从源系统暂存的各类对象执行一系列查询,以保持数据同步并自行生成增量标记——因为源系统不提供该功能,且我们无法对其进行修改或构建。
我将所有数据加载至内存后,执行如下查询:找出源系统中已不存在但仍存在于暂存数据库中的对象,并将其标记为“已删除”。当前LINQ查询的瓶颈在于.Contains()部分,如何优化其性能?比如使用带自定义比较器的.Except(),或是将数据存入哈希列表后再进行比较?
问题在于后续需要对暂存对象执行属性转换操作,当前方案虽简单,但在处理20000个对象时速度极慢。
stagedSystemObjects.Where(stagedSystemObject => !sourceSystemObjects.Select(sourceSystemObject => sourceSystemObject.Code) .Contains(stagedSystemObject.Code) ) .Select(x => { x.ActiveStatus = ActiveStatuses.Disabled; x.ChangeReason = ChangeReasons.Edited; return x; }) .ToList();
嘿,这个性能瓶颈其实很好解决!你当前的代码慢在每次调用Contains()时,都会遍历一遍sourceSystemObjects.Select(...)的结果——相当于2万条暂存对象,每条都要遍历2万条源对象的Code,总共有4亿次比较,不慢才怪😅
我给你两个最实用的优化方向,都能把速度拉到秒级:
方案一:用HashSet预存源系统Code
HashSet的Contains()是常数时间查找,也就是O(1),整体时间复杂度直接降到O(n+m),效率提升巨大。代码调整如下:
// 先把源系统的Code一次性提取到HashSet里,只遍历一次源数据 var sourceCodes = new HashSet<string>(sourceSystemObjects.Select(s => s.Code)); // 用HashSet快速过滤出需要标记的对象 var toDisable = stagedSystemObjects.Where(staged => !sourceCodes.Contains(staged.Code)) .ToList(); // 批量修改属性(单独循环比在LINQ的Select里修改更清晰,也避免额外开销) foreach (var item in toDisable) { item.ActiveStatus = ActiveStatuses.Disabled; item.ChangeReason = ChangeReasons.Edited; }
方案二:用.Except()求差集
如果你更倾向于LINQ的链式风格,.Except()也是绝佳选择——它内部其实也是用哈希表实现的,效率同样很高。
方式1:只比较Code
如果只需要通过Code判断存在性,可以直接对Code集合求差集:
// 提取两边的Code集合,求暂存中有但源系统没有的Code var missingCodes = stagedSystemObjects.Select(s => s.Code) .Except(sourceSystemObjects.Select(s => s.Code)) .ToHashSet(); // 再过滤出对应的暂存对象 var toDisable = stagedSystemObjects.Where(staged => missingCodes.Contains(staged.Code)) .ToList(); // 批量修改属性 foreach (var item in toDisable) { item.ActiveStatus = ActiveStatuses.Disabled; item.ChangeReason = ChangeReasons.Edited; }
方式2:自定义对象比较器
如果你需要直接比较整个对象(而不仅仅是Code),可以自定义一个相等比较器,然后用.Except()直接筛选对象:
// 自定义比较器,根据Code判断对象是否相等 public class ObjectCodeComparer : IEqualityComparer<YourObjectType> { public bool Equals(YourObjectType x, YourObjectType y) { if (ReferenceEquals(x, y)) return true; if (x is null || y is null) return false; return x.Code == y.Code; } public int GetHashCode(YourObjectType obj) { // 用Code的哈希值作为对象的哈希值,确保哈希表能正确分组 return obj.Code?.GetHashCode() ?? 0; } } // 直接用Except筛选出源系统没有的暂存对象 var toDisable = stagedSystemObjects.Except(sourceSystemObjects, new ObjectCodeComparer()) .ToList(); // 批量修改属性 foreach (var item in toDisable) { item.ActiveStatus = ActiveStatuses.Disabled; item.ChangeReason = ChangeReasons.Edited; }
这两种方案都能完美解决你当前的性能问题,处理2万条数据绝对是秒级完成。另外提醒下,尽量避免在LINQ的Select里直接修改对象属性,单独循环修改会更清晰易维护哦!
内容的提问来源于stack exchange,提问作者Yves Schelpe

