C#从SQL Server加载20万条记录到字典的最优性能方案
优化方案:提升嵌套字典构建性能
原代码的核心性能瓶颈在于用FirstOrDefault遍历List查找子项(时间复杂度O(n)),加上代码里的逻辑错误导致重复创建/添加对象,进一步拖慢了处理速度。以下是针对性的优化措施:
关键优化点
- 用Dictionary替代List存储子项:将
AgentRecord.Locations改为Dictionary<int, LocationSF>(以LocIdSeq为键),LocationSF.Licenses改为Dictionary<int, LicenseSF>(以LicenseId为键),查找操作从O(n)降为O(1)。 - 提前初始化集合:在类的构造函数中直接初始化字典/集合,避免每次判断
null的开销。 - 修正逻辑错误:原代码中存在多处字段匹配错误(比如用
cusId匹配LocIdSeq、从agentSF而非locSF找许可证),这些错误会导致重复创建大量冗余对象,必须修正。 - 减少不必要的类型转换:
LicenseId本身是int类型,无需转成string存储,直接用int作为键和属性类型,节省转换开销。 - 简化DBNull判断:直接使用
SqlDataReader.IsDBNull,避免Convert.IsDBNull的额外调用。
优化后的代码实现
修正后的实体类
public class LicenseSF { public int LicenseId { get; set; } } public class LocationSF { public int LocIdSeq { get; set; } // 用Dictionary存储许可证,查找更快 public Dictionary<int, LicenseSF> Licenses { get; set; } = new Dictionary<int, LicenseSF>(); } public class AgentRecord { public int CusIdSeq { get; set; } // 用Dictionary存储地点,查找更快 public Dictionary<int, LocationSF> Locations { get; set; } = new Dictionary<int, LocationSF>(); }
数据读取与字典构建代码
Dictionary<int, AgentRecord> agencyDict = new Dictionary<int, AgentRecord>(); using (var con = new SqlConnection(connectionString)) { using (var cmd = new SqlCommand(sql, con)) { await con.OpenAsync(); using (var reader = await cmd.ExecuteReaderAsync()) { // 提前获取列索引,避免每次读取时重复查找列位置 var cusIdIdx = reader.GetOrdinal("CusIdSeq"); var locIdSeqIdx = reader.GetOrdinal("LocIdSeq"); var licenseIdIdx = reader.GetOrdinal("LicenseId"); var cusNameIdx = reader.GetOrdinal("CUS_Name"); while (await reader.ReadAsync()) { // 读取核心字段,简化DBNull判断 int cusId = reader.IsDBNull(cusIdIdx) ? -1 : reader.GetInt32(cusIdIdx); int locIdSeq = reader.IsDBNull(locIdSeqIdx) ? -1 : reader.GetInt32(locIdSeqIdx); int licenseId = reader.IsDBNull(licenseIdIdx) ? -1 : reader.GetInt32(licenseIdIdx); string cusName = reader.IsDBNull(cusNameIdx) ? string.Empty : reader.GetString(cusNameIdx); // 获取或创建Agent if (!agencyDict.TryGetValue(cusId, out var agentSF)) { agentSF = new AgentRecord { CusIdSeq = cusId }; agencyDict[cusId] = agentSF; } // 处理地点(仅当LocIdSeq有效时) if (locIdSeq > 0) { // 获取或创建Location if (!agentSF.Locations.TryGetValue(locIdSeq, out var locSF)) { locSF = new LocationSF { LocIdSeq = locIdSeq }; agentSF.Locations[locIdSeq] = locSF; } // 处理许可证(仅当LicenseId有效时) if (licenseId > 0) { // 获取或创建License if (!locSF.Licenses.TryGetValue(licenseId, out var licSF)) { licSF = new LicenseSF { LicenseId = licenseId }; locSF.Licenses[licenseId] = licSF; } } } } } } }
额外性能建议
- 优化SQL查询:26个左连接会返回大量冗余数据(笛卡尔积),可以考虑将查询拆分为3个独立的查询(客户、地点、许可证),分别读取后在内存中关联,减少返回的总行数,降低网络传输和内存开销。
- 启用CommandBehavior.SequentialAccess:如果不需要随机访问列,可以在
ExecuteReaderAsync中添加CommandBehavior.SequentialAccess,减少内存占用,提升读取速度。 - 实体类优化:如果不需要变更属性,可以将实体类改为
readonly struct(值类型),减少GC压力。
内容的提问来源于stack exchange,提问作者WorkJ
相关产品推荐
相关产品推荐

