ASP.NET Core 6.0根据appsettings配置动态切换泛型仓储实现
动态切换泛型仓储的实现方案
基础实现(改配置后重启生效,无需重编译)
不需要修改现有仓储的业务逻辑,只要把硬编码的DI注册逻辑改成运行时读取配置、反射加载对应类型即可,步骤如下:
- 首先调整
appsettings.json的配置结构,明确指定当前启用的数据库提供方,同时配置好提供方和对应仓储类型的映射关系,注意开放泛型类型的CLR名称末尾要带`1标识(代表该类型有1个泛型参数):
{ "SourceDb": { "CurrentProvider": "Entity", "ProviderMappings": { "Entity": "你的项目命名空间.Repositories.EntityRepository`1", "Mongo": "你的项目命名空间.Repositories.MongoRepository`1" } } }
- 替换Program.cs中原先硬编码的DI注册代码,改为启动时读取配置、反射匹配对应仓储类型完成注册:
// 读取SourceDb配置节点 var dbConfigSection = builder.Configuration.GetSection("SourceDb"); var currentProvider = dbConfigSection.GetValue<string>("CurrentProvider"); var providerMappings = dbConfigSection.GetSection("ProviderMappings").Get<Dictionary<string, string>>() ?? throw new InvalidOperationException("未配置数据库提供方映射规则"); // 校验配置合法性 if (!providerMappings.TryGetValue(currentProvider, out var repoTypeFullName)) { throw new InvalidOperationException($"当前配置的数据库提供方{currentProvider}无对应仓储映射"); } // 从已加载的程序集中查找匹配的开放泛型仓储类型 var targetRepositoryType = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(asm => asm.GetTypes()) .FirstOrDefault(t => t.FullName == repoTypeFullName && t.IsGenericTypeDefinition) ?? throw new InvalidOperationException($"未找到仓储类型{repoTypeFullName},请检查命名空间配置"); // 完成泛型仓储注册 builder.Services.AddTransient(typeof(IRepository<>), targetRepositoryType);
这个方案实现后,完全不需要重新编译代码:应用发布后,只要修改服务器上appsettings.json里的CurrentProvider值为Mongo或Entity,保存后重启应用即可自动切换到对应数据库的仓储实现。
进阶优化(无需重启应用,配置热更新自动切换)
如果场景不允许重启应用,可以通过代理层实现运行时动态切换,原理是引入一个中间代理实现IRepository<>接口,每次调用仓储方法时实时读取最新配置,转发给对应数据库的仓储实例:
- 首先定义强类型配置类用于配置热更新:
public class SourceDbOptions { public string CurrentProvider { get; set; } = string.Empty; public Dictionary<string, string> ProviderMappings { get; set; } = new(); }
- 实现仓储代理类,所有接口方法全部转发给当前配置对应的真实仓储实例:
public class RepositoryProxy<T> : IRepository<T> { private readonly IServiceProvider _serviceProvider; private readonly IOptionsMonitor<SourceDbOptions> _optionsMonitor; public RepositoryProxy(IServiceProvider serviceProvider, IOptionsMonitor<SourceDbOptions> optionsMonitor) { _serviceProvider = serviceProvider; _optionsMonitor = optionsMonitor; } /// <summary> /// 实时获取当前配置对应的真实仓储实例 /// </summary> private IRepository<T> ResolveCurrentRepository() { var config = _optionsMonitor.CurrentValue; if (!config.ProviderMappings.TryGetValue(config.CurrentProvider, out var repoTypeFullName)) { throw new InvalidOperationException($"无效的数据库提供方配置:{config.CurrentProvider}"); } var repoGenericType = Type.GetType(repoTypeFullName) ?? throw new InvalidOperationException($"加载仓储类型{repoTypeFullName}失败"); var repoType = repoGenericType.MakeGenericType(typeof(T)); return (IRepository<T>)_serviceProvider.GetRequiredService(repoType); } // 以下为IRepository<T>接口实现,全部转发给真实仓储 public Task<T?> GetByIdAsync(object id) => ResolveCurrentRepository().GetByIdAsync(id); public Task AddAsync(T entity) => ResolveCurrentRepository().AddAsync(entity); public Task UpdateAsync(T entity) => ResolveCurrentRepository().UpdateAsync(entity); public Task DeleteAsync(object id) => ResolveCurrentRepository().DeleteAsync(entity); // 其余接口方法按照相同逻辑转发即可 }
- 修改DI注册逻辑,同时注册两个真实仓储、配置热更新和代理层:
// 注册两个仓储实现到容器,供代理动态解析 builder.Services.AddTransient(typeof(EntityRepository<>)); builder.Services.AddTransient(typeof(MongoRepository<>)); // 开启配置热更新 builder.Services.Configure<SourceDbOptions>(builder.Configuration.GetSection("SourceDb")); // 注册代理作为IRepository<>的对外实现 builder.Services.AddTransient(typeof(IRepository<>), typeof(RepositoryProxy<>));
注意:热切换方案需要额外处理数据库连接生命周期、事务一致性等问题,数据库切换本身属于低频率运维操作,绝大多数生产场景用第一种基础方案即可,实现简单稳定性更高。
内容的提问来源于stack exchange,提问作者Mikhail Holubeu
相关产品推荐
相关产品推荐

