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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 04:18:18