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

HotChocolate GraphQL多租户场景下如何传递租户ID生成动态DbContext连接串

解决方案

1. 存储租户ID到请求生命周期上下文

首先定义Scoped作用域的租户访问器,用于在单次请求范围内传递租户ID:

public class TenantAccessor
{
    public string? TenantId { get; set; }
}

在服务注册时将其标记为Scoped:
builder.Services.AddScoped<TenantAccessor>();

然后实现HotChocolate的HTTP请求拦截器,从请求头/认证Claims/路由参数中提取租户ID,存入访问器:

public class TenantHttpRequestInterceptor : IHttpRequestInterceptor
{
    public async ValueTask OnRequestAsync(IHttpContextAccessor httpContextAccessor, 
        IRequestExecutor requestExecutor, 
        IQueryRequestBuilder requestBuilder, 
        CancellationToken cancellationToken)
    {
        var httpContext = httpContextAccessor.HttpContext;
        // 示例从请求头X-Tenant-Id提取,可根据实际场景替换为从认证信息/路由提取
        if (httpContext?.Request.Headers.TryGetValue("X-Tenant-Id", out var tenantId) == true)
        {
            var tenantAccessor = httpContext.RequestServices.GetRequiredService<TenantAccessor>();
            tenantAccessor.TenantId = tenantId.FirstOrDefault();
        }
        // 可在此处增加租户ID非空校验,直接返回错误避免后续逻辑执行
    }
}

将拦截器注册到GraphQL服务:

builder.Services.AddGraphQLServer()
    .AddHttpRequestInterceptor<TenantHttpRequestInterceptor>();

2. 动态构造租户对应的DbContext

不要在启动时注册固定连接字符串的DbContext,改用Scoped工厂模式动态构造:

// 注册TenantStructure库的DbContext
builder.Services.AddScoped<TenantStructureDbContext>(sp =>
{
    var tenantAccessor = sp.GetRequiredService<TenantAccessor>();
    if (string.IsNullOrWhiteSpace(tenantAccessor.TenantId))
    {
        throw new InvalidOperationException("缺少有效租户ID");
    }
    // 从配置读取带占位符的连接字符串模板
    var connTemplate = sp.GetRequiredService<IConfiguration>().GetConnectionString("TenantStructureTemplate");
    var finalConnStr = string.Format(connTemplate, tenantAccessor.TenantId);
    return new TenantStructureDbContext(new DbContextOptionsBuilder<TenantStructureDbContext>()
        .UseSqlServer(finalConnStr)
        .Options);
});

// TenantData库的DbContext按照同样逻辑注册即可

该方式会在单次请求内首次用到DbContext时完成构造,同一个请求内多次查询复用同一个DbContext实例,无额外性能开销。

3. Resolver中直接使用DbContext

不需要在GraphQL查询参数中声明租户ID,直接注入对应DbContext即可使用:

[QueryType]
public static class BusinessQueries
{
    public static async Task<List<MyBusinessData>> GetBusinessListAsync(
        [Service] TenantDataDbContext dataDb,
        CancellationToken ct)
    {
        return await dataDb.MyBusinessData.ToListAsync(ct);
    }
}

注意事项

  • 租户ID提取后建议增加格式校验,避免拼接连接字符串时出现注入风险
  • 若有跨租户操作需求,可以单独注册Transient的DbContext工厂,手动传入租户ID构造对应实例
  • 可以配合HotChocolate的全局中间件做租户权限校验,避免非法租户ID访问

内容的提问来源于stack exchange,提问作者Holger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 03:15:08