You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用C#一次性保存配置路径、属性及对应值(含集合类型)

如何使用C#一次性保存配置路径、属性及对应值(含集合类型)

我完全懂你现在的困扰——每次要逐个处理配置类的属性,还要单独写逻辑处理集合,不仅代码冗余,频繁的数据库调用还拖慢了性能。咱们可以用递归遍历+批量数据库操作的思路,一次性把整个配置对象(包括嵌套的集合)的所有键值对都处理完,只需要少数几次数据库交互就能搞定。

核心思路

  1. 写一个递归方法,自动遍历配置对象的所有属性(包括嵌套对象、集合),生成符合要求的配置路径(比如Partners:Krone:PartLocator:Manufacturers:0:Identity)和对应值
  2. 把所有生成的键值对批量提交到数据库,对比现有数据后批量添加/更新,最后统一提交变更,减少数据库调用次数

具体实现步骤

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 12:24:08