Blazor Server+EF Core 8:如何通过DBContextFactory调用RCL的DBContext?
实现方案与替代方案
一、合理实现方式:动态DbContext工厂+接口解耦
1. 定义共享的动态工厂接口(project.shared)
在共享项目中定义通用的DbContext工厂接口,让主项目实现、RCL依赖该接口,实现解耦:
public interface IDynamicDbContextFactory<TContext> where TContext : DbContext { TContext Create(string databaseName); }
2. 主项目实现动态工厂并注册
在project.ui中实现工厂类,负责根据数据库名动态构建连接字符串并创建RCL的DbContext:
public class DynamicDbContextFactory<TContext> : IDynamicDbContextFactory<TContext> where TContext : DbContext { private readonly IConfiguration _config; private readonly DbContextOptions<TContext> _baseOptions; public DynamicDbContextFactory(IConfiguration config, DbContextOptions<TContext> baseOptions) { _config = config; _baseOptions = baseOptions; } public TContext Create(string databaseName) { // 从配置读取连接字符串模板,替换数据库名 var connTemplate = _config.GetConnectionString("DefaultConnectionTemplate"); var connString = string.Format(connTemplate, databaseName); // 基于基础选项构建新的上下文配置 var optionsBuilder = new DbContextOptionsBuilder<TContext>(_baseOptions); optionsBuilder.UseSqlServer(connString); // 反射创建DbContext实例(确保RCL的DbContext有对应构造函数) return (TContext)Activator.CreateInstance(typeof(TContext), optionsBuilder.Options); } }
在Program.cs中注册服务:
// 注册RCL DbContext的基础配置(如日志、拦截器) builder.Services.AddDbContextOptions<RclDbContext>(); // 注册动态工厂 builder.Services.AddScoped<IDynamicDbContextFactory<RclDbContext>, DynamicDbContextFactory<RclDbContext>>();
3. RCL Data项目配置DbContext
确保rcl.data中的DbContext提供接受DbContextOptions<T>的构造函数:
public class RclDbContext : DbContext { public RclDbContext(DbContextOptions<RclDbContext> options) : base(options) { } // 对应的数据表DbSet public DbSet<DepartmentEntity> DepartmentEntities { get; set; } }
4. RCL UI/Data中使用工厂获取上下文
在RCL的业务服务中注入工厂,结合当前部门信息创建对应数据库的上下文:
public class RclDataService { private readonly IDynamicDbContextFactory<RclDbContext> _contextFactory; private readonly string _currentDeptDbName; // 通过用户会话或配置获取当前部门对应的数据库名 public RclDataService(IDynamicDbContextFactory<RclDbContext> contextFactory, ICurrentUserSession userSession) { _contextFactory = contextFactory; _currentDeptDbName = userSession.GetDepartmentDatabaseName(); } public async Task<List<DepartmentEntity>> GetEntitiesAsync() { using var context = _contextFactory.Create(_currentDeptDbName); return await context.DepartmentEntities.ToListAsync(); } }
二、替代方案
1. EF Core拦截器实现动态数据库切换
通过EF Core的命令拦截器,在执行数据库操作前动态修改连接字符串的数据库名称,无需创建多个DbContext实例:
public class TenantDatabaseInterceptor : DbCommandInterceptor { private readonly string _targetDatabase; public TenantDatabaseInterceptor(string targetDatabase) { _targetDatabase = targetDatabase; } public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result) { ModifyConnectionDatabase(command); return base.ReaderExecuting(command, eventData, result); } private void ModifyConnectionDatabase(DbCommand command) { if (command.Connection != null && !string.IsNullOrEmpty(_targetDatabase)) { command.Connection.ChangeDatabase(_targetDatabase); } } }
注册时结合Scoped服务注入当前部门数据库名,确保每个用户会话对应正确的数据库:
builder.Services.AddScoped(provider => { var userSession = provider.GetRequiredService<ICurrentUserSession>(); return new TenantDatabaseInterceptor(userSession.GetDepartmentDatabaseName()); }); builder.Services.AddDbContext<RclDbContext>((sp, options) => { var connString = sp.GetRequiredService<IConfiguration>().GetConnectionString("DefaultConnection"); options.UseSqlServer(connString) .AddInterceptors(sp.GetRequiredService<TenantDatabaseInterceptor>()); });
2. 预注册多DbContext实例(适合部门数量固定场景)
如果部门数量固定,可在主项目中直接为每个部门注册对应的DbContext实例,通过命名服务区分:
// 假设从配置读取所有部门数据库配置 var deptDatabases = _config.GetSection("DepartmentDatabases").Get<List<string>>(); foreach (var dbName in deptDatabases) { var connString = string.Format(_config.GetConnectionString("DefaultConnectionTemplate"), dbName); builder.Services.AddDbContext<RclDbContext>(options => options.UseSqlServer(connString), serviceKey: dbName); }
使用时通过IServiceProvider获取命名服务:
public class RclDataService { private readonly IServiceProvider _serviceProvider; private readonly string _currentDeptDbName; public RclDataService(IServiceProvider serviceProvider, ICurrentUserSession userSession) { _serviceProvider = serviceProvider; _currentDeptDbName = userSession.GetDepartmentDatabaseName(); } public async Task<List<DepartmentEntity>> GetEntitiesAsync() { using var context = _serviceProvider.GetRequiredKeyedService<RclDbContext>(_currentDeptDbName); return await context.DepartmentEntities.ToListAsync(); } }
注意事项
- Blazor Server生命周期:确保获取当前部门的服务(如
ICurrentUserSession)为Scoped,避免跨用户串库。 - DbContext释放:使用
using块管理DbContext生命周期,防止内存泄漏。 - 配置安全:连接字符串模板应存储在安全配置源(如Azure Key Vault),避免明文暴露。
内容的提问来源于stack exchange,提问作者Jilael
相关产品推荐
相关产品推荐

