如何使用C#一次性保存配置路径、属性及对应值(含集合类型)
如何使用C#一次性保存配置路径、属性及对应值(含集合类型)
我完全懂你现在的困扰——每次要逐个处理配置类的属性,还要单独写逻辑处理集合,不仅代码冗余,频繁的数据库调用还拖慢了性能。咱们可以用递归遍历+批量数据库操作的思路,一次性把整个配置对象(包括嵌套的集合)的所有键值对都处理完,只需要少数几次数据库交互就能搞定。
核心思路
- 写一个递归方法,自动遍历配置对象的所有属性(包括嵌套对象、集合),生成符合要求的配置路径(比如
Partners:Krone:PartLocator:Manufacturers:0:Identity)和对应值 - 把所有生成的键值对批量提交到数据库,对比现有数据后批量添加/更新,最后统一提交变更,减少数据库调用次数
具体实现步骤
1. 编写递归扁平化配置的方法
这个方法会自动处理普通值类型、嵌套对象、集合类型,生成标准的配置路径和值:
using System.Reflection; using Microsoft.Extensions.Configuration; private IEnumerable<(string ConfigPath, string? Value)> FlattenConfiguration(object configObj, string currentPath) { if (configObj == null) yield break; // 获取对象的所有公共实例属性 var properties = configObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { var propValue = prop.GetValue(configObj); var propPath = ConfigurationPath.Combine(currentPath, prop.Name); // 处理集合类型(跳过string,因为string本身也是IEnumerable<char>) if (propValue is IEnumerable<object> collection && !(propValue is string)) { int index = 0; foreach (var item in collection) { // 集合元素的路径要加上索引,比如Manufacturers:0 var itemPath = ConfigurationPath.Combine(propPath, index.ToString()); // 递归遍历集合元素的属性 foreach (var nestedItem in FlattenConfiguration(item, itemPath)) { yield return nestedItem; } index++; } } // 处理嵌套的复杂对象(非值类型、非string) else if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { foreach (var nestedItem in FlattenConfiguration(propValue, propPath)) { yield return nestedItem; } } // 处理普通值类型(包括可空类型) else { yield return (propPath, propValue?.ToString()); } } }
2. 编写批量数据库处理方法
把原来的单条处理改成批量操作,先一次性查询所有现有配置,再对比后批量添加/更新:
private async Task BatchUpdateConfigurationAsync(IEnumerable<(string ConfigPath, string? Value)> configEntries, CancellationToken cancellationToken) { try { // 一次性获取所有需要处理的配置键对应的现有数据 var allTargetKeys = configEntries.Select(e => e.ConfigPath).ToList(); var existingEntries = await _configurationRepository.GetAllAspenDatabaseItemsAsync(_databaseIdentity, allTargetKeys, cancellationToken); var existingEntryDict = existingEntries.ToDictionary(e => e.Key, e => e); var entriesToAdd = new List<AspenDatabaseConfigurationItem>(); var entriesToUpdate = new List<AspenDatabaseConfigurationItem>(); foreach (var entry in configEntries) { // 根据业务需求决定是否跳过空值 if (string.IsNullOrEmpty(entry.Value)) continue; if (existingEntryDict.TryGetValue(entry.ConfigPath, out var existingItem)) { // 只有值发生变化时才更新 if (existingItem.Value != entry.Value) { existingItem.Value = entry.Value; entriesToUpdate.Add(existingItem); } } else { // 新增配置条目 entriesToAdd.Add(new AspenDatabaseConfigurationItem { Key = entry.ConfigPath, DatabaseIdentity = _databaseIdentity, Value = entry.Value }); } } // 批量添加新条目 if (entriesToAdd.Any()) { _configurationRepository.AddRangeConfigurationItem(_databaseIdentity, entriesToAdd); } // 批量更新现有条目 if (entriesToUpdate.Any()) { await _configurationRepository.UpdateRangeAspenDatabaseItemsAsync(entriesToUpdate, cancellationToken); } // 如果你的仓储有统一的保存方法,这里调用一次即可(比如EF的SaveChangesAsync) // await _configurationRepository.SaveChangesAsync(cancellationToken); } finally { _logger.LogMethodEnd(); } }
3. 修改原有方法,实现一次性处理
把原来逐个处理属性的逻辑替换成调用上面两个方法,一次性处理整个配置对象:
private async Task updateMainConfigurationAsync(PartLocatorOptions configuration, CancellationToken cancellationToken) { try { // 生成整个PartLocatorOptions的扁平化配置键值对 var allConfigEntries = FlattenConfiguration(configuration, PartLocatorOptions.ConfigurationKey); // 批量提交到数据库 await BatchUpdateConfigurationAsync(allConfigEntries, cancellationToken); } finally { _logger.LogMethodEnd(); } }
关键细节说明
- 集合处理:递归方法会自动给集合元素加上索引(比如
Manufacturers:0、Manufacturers:1),生成的路径完全符合你数据库里的存储格式 - 性能优化:批量操作把原来N次数据库调用缩减为3次左右(查询现有数据、批量添加、批量更新),大幅提升效率
- 扩展性:这个递归方法是通用的,不管你的配置类新增多少属性、嵌套多少层级,都不需要修改遍历逻辑,直接复用即可
备注:内容来源于stack exchange,提问作者Kamran Khan
相关产品推荐
相关产品推荐

