如何优化统计戒指尺寸数量的LINQ查询,使其更简洁复用性更高?
原有代码的核心问题
- 重复遍历戒指尺寸集合近30次,数据量大时性能损耗极高
- 所有尺寸值和赋值逻辑全部硬编码,后续新增/删除尺寸需要修改大量代码,维护成本很高
- 缺少空值兼容处理,selector返回null时可能出现匹配异常
优化实现方案
我们可以通过LINQ的GroupBy一次性统计所有尺寸的数量,仅遍历集合1次,根据是否能修改Dto结构分为两种场景:
场景1:不修改现有RingSizeLettersDto定义
如果Dto已有外部依赖、接口契约固定无法调整结构,用单次分组统计结果后统一赋值即可:
private async Task<RingSizeLettersDto> CountOfLettersByRingFingerAsync(Func<RingSize, string> selector) { var ringSizes = await _ringSizeRepository.Get(); // 一次性分组统计所有尺寸的数量,仅遍历集合1次 var countDict = ringSizes .GroupBy(x => selector(x)?.Trim() ?? "N/A") .ToDictionary(g => g.Key, g => g.Count()); var ringSizeLetters = new RingSizeLettersDto(); ringSizeLetters.G = countDict.TryGetValue("G", out var gCnt) ? gCnt : 0; ringSizeLetters.H = countDict.TryGetValue("H", out var hCnt) ? hCnt : 0; ringSizeLetters.I = countDict.TryGetValue("I", out var iCnt) ? iCnt : 0; ringSizeLetters.J = countDict.TryGetValue("J", out var jCnt) ? jCnt : 0; ringSizeLetters.K = countDict.TryGetValue("K", out var kCnt) ? kCnt : 0; ringSizeLetters.L = countDict.TryGetValue("L", out var lCnt) ? lCnt : 0; ringSizeLetters.M = countDict.TryGetValue("M", out var mCnt) ? mCnt : 0; ringSizeLetters.N = countDict.TryGetValue("N", out var nCnt) ? nCnt : 0; ringSizeLetters.O = countDict.TryGetValue("O", out var oCnt) ? oCnt : 0; ringSizeLetters.P = countDict.TryGetValue("P", out var pCnt) ? pCnt : 0; ringSizeLetters.Q = countDict.TryGetValue("Q", out var qCnt) ? qCnt : 0; ringSizeLetters.R = countDict.TryGetValue("R", out var rCnt) ? rCnt : 0; ringSizeLetters.S = countDict.TryGetValue("S", out var sCnt) ? sCnt : 0; ringSizeLetters.T = countDict.TryGetValue("T", out var tCnt) ? tCnt : 0; ringSizeLetters.U = countDict.TryGetValue("U", out var uCnt) ? uCnt : 0; ringSizeLetters.V = countDict.TryGetValue("V", out var vCnt) ? vCnt : 0; ringSizeLetters.W = countDict.TryGetValue("W", out var wCnt) ? wCnt : 0; ringSizeLetters.X = countDict.TryGetValue("X", out var xCnt) ? xCnt : 0; ringSizeLetters.Y = countDict.TryGetValue("Y", out var yCnt) ? yCnt : 0; ringSizeLetters.Z = countDict.TryGetValue("Z", out var zCnt) ? zCnt : 0; ringSizeLetters.Z1 = countDict.TryGetValue("Z1", out var z1Cnt) ? z1Cnt : 0; ringSizeLetters.Z2 = countDict.TryGetValue("Z2", out var z2Cnt) ? z2Cnt : 0; ringSizeLetters.Z3 = countDict.TryGetValue("Z3", out var z3Cnt) ? z3Cnt : 0; ringSizeLetters.Z4 = countDict.TryGetValue("Z4", out var z4Cnt) ? z4Cnt : 0; ringSizeLetters.Z5 = countDict.TryGetValue("Z5", out var z5Cnt) ? z5Cnt : 0; ringSizeLetters.Z6 = countDict.TryGetValue("Z6", out var z6Cnt) ? z6Cnt : 0; ringSizeLetters.NA = countDict.TryGetValue("N/A", out var naCnt) ? naCnt : 0; return ringSizeLetters; }
如果想要进一步减少重复代码,可以预先建立尺寸和Dto属性的映射关系,通过反射自动赋值,后续新增尺寸仅需要更新映射列表即可:
// 尺寸与Dto属性的映射全局仅需要初始化一次 private static readonly Dictionary<string, PropertyInfo> _sizePropertyMap = typeof(RingSizeLettersDto) .GetProperties() .ToDictionary( p => p.Name == "NA" ? "N/A" : p.Name, p => p ); private async Task<RingSizeLettersDto> CountOfLettersByRingFingerAsync(Func<RingSize, string> selector) { var ringSizes = await _ringSizeRepository.Get(); var countDict = ringSizes .GroupBy(x => selector(x)?.Trim() ?? "N/A") .ToDictionary(g => g.Key, g => g.Count()); var ringSizeLetters = new RingSizeLettersDto(); foreach (var (sizeKey, property) in _sizePropertyMap) { property.SetValue(ringSizeLetters, countDict.TryGetValue(sizeKey, out var cnt) ? cnt : 0); } return ringSizeLetters; }
场景2:可调整RingSizeLettersDto定义
这是最优方案,直接把计数逻辑改成字典存储,后续新增尺寸完全不需要修改业务代码:
// 调整后的Dto定义,原有快捷访问属性可保留兼容旧逻辑 public class RingSizeLettersDto { public Dictionary<string, int> SizeCounts { get; set; } = new(); public int G => SizeCounts.TryGetValue("G", out var cnt) ? cnt : 0; public int H => SizeCounts.TryGetValue("H", out var cnt) ? cnt : 0; // 其他原有尺寸属性同理保留即可 } // 优化后的业务代码 private async Task<RingSizeLettersDto> CountOfLettersByRingFingerAsync(Func<RingSize, string> selector) { var ringSizes = await _ringSizeRepository.Get(); var countDict = ringSizes .GroupBy(x => selector(x)?.Trim() ?? "N/A") .ToDictionary(g => g.Key, g => g.Count()); return new RingSizeLettersDto { SizeCounts = countDict }; }
额外性能优化建议
如果你使用的是EF/EF Core这类ORM,_ringSizeRepository.Get()返回的是IQueryable<RingSize>,可以把参数从Func<RingSize, string>改成Expression<Func<RingSize, string>>,这样分组操作会直接在数据库层面执行,不需要拉取全量数据到内存计算,性能提升非常明显。
内容的提问来源于stack exchange,提问作者Morpheus
相关产品推荐
相关产品推荐

