依赖注入(DI):如何正确实现基于URL参数的动态数据库访问
优化方案
你遇到的是典型的按请求动态解析多租户数据库的场景,以下是两种可直接落地的优化方案,完全解决手动调用Init、Repository无法注入的问题:
方案1:基于IHttpContextAccessor的工厂注入(最适合你的场景)
这个方案直接把数据库名解析逻辑移到DI容器的实例化工厂中,完全不需要手动初始化步骤:
- 首先注册HTTP上下文访问器,用于在DI工厂中获取当前请求的域名信息:
// Program.cs 服务注册部分 builder.Services.AddHttpContextAccessor();
- 注册
ILiteDbRepository时使用工厂委托,动态解析当前请求对应的数据库名:
// Program.cs 服务注册部分 builder.Services.AddScoped<ILiteDbRepository>(sp => { var httpAccessor = sp.GetRequiredService<IHttpContextAccessor>(); var host = httpAccessor.HttpContext?.Request.Host.Host ?? "default"; var dbName = host.Split('.').First(); // 可在此处添加dbName合法性校验,防止非法访问 return new LiteDbRepository(dbName); });
- 改造
DatabaseService,直接把Repository注入构造函数,删除Init方法:
public class DatabaseService : IDatabaseService { private readonly ILogger<DatabaseService> _logger; private readonly ILiteDbRepository _repository; public DatabaseService(ILogger<DatabaseService> logger, ILiteDbRepository repository) { _logger = logger; _repository = repository; } // 后续业务逻辑直接使用_repository即可 }
- 控制器基类删除
Init调用逻辑,只需保留注入即可:
public class LoadDatabaseControllerBase : Controller { protected IDatabaseService Database; public LoadDatabaseControllerBase(IDatabaseService databaseService) { Database = databaseService; } }
方案2:基于Scoped配置对象的方案(兼容非HTTP场景)
如果你的DatabaseService还需要给后台定时任务、消息消费等非HTTP场景使用,可以用你提到的配置类方案:
- 定义数据库配置类:
public class DatabaseOptions { public string DatabaseName { get; set; } }
- 注册配置类和对应服务:
// Program.cs 服务注册部分 builder.Services.AddScoped<DatabaseOptions>(); builder.Services.AddScoped<ILiteDbRepository>(sp => { var options = sp.GetRequiredService<DatabaseOptions>(); if (string.IsNullOrWhiteSpace(options.DatabaseName)) { throw new InvalidOperationException("未配置数据库名称,请先赋值DatabaseOptions.DatabaseName"); } return new LiteDbRepository(options.DatabaseName); }); builder.Services.AddScoped<IDatabaseService, DatabaseService>();
- 在请求管道中给配置类赋值,可选中间件或者控制器基类的
OnActionExecuting:
public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); var dbOptions = context.HttpContext.RequestServices.GetRequiredService<DatabaseOptions>(); dbOptions.DatabaseName = Request.Host.Host.Split('.').First(); }
这个方案里配置类是请求级别的Scoped生命周期,同一个请求内的所有服务都会拿到同一个配置,不需要手动调用任何Init方法。
注意事项
- 两个方案中,
DatabaseService和ILiteDbRepository都必须注册为Scoped(请求级)或Transient(临时)生命周期,不能注册为单例,否则会出现不同请求串库的问题。 - 数据库名解析逻辑可以统一放到公共方法中,方便后续规则调整。
内容的提问来源于stack exchange,提问作者Stitch10925
相关产品推荐
相关产品推荐

