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

如何在EF Core中为SQL行级安全(RLS)设置会话上下文 多租户Azure场景

最高效方案实现指引

首选方案:加密租户标识内嵌租户ID(性能最优)

直接在生成加密租户标识时,将整数类型的租户ID作为明文的一部分加密到字符串中。服务端拿到加密串后,使用对称加密算法(如AES-256)直接解密即可拿到租户ID,无需任何额外的数据库查询或映射查找,性能损耗几乎可以忽略。

注意:加密密钥建议存储在Azure Key Vault中,不要硬编码到应用代码内,避免密钥泄露风险。

次选方案:二级缓存存储租户映射(适配加密串无法修改的场景)

如果现有加密租户标识的明文内容无法调整,只能存放租户业务标识(如租户编码),则采用二级缓存方案避免每次请求查询数据库:

  • 客户端将加密租户标识放在自定义请求头(如X-Encrypted-Tenant)中传递,避免放在URL产生泄露风险
  • 服务端解密得到原始租户业务标识后,先查询当前请求级存储HttpContext.Items,如果本次请求已经解析过租户ID直接复用
  • 再查询内存缓存(单实例部署)或Azure Redis Cache(多实例部署),如果命中缓存直接返回对应租户ID
  • 缓存未命中时才查询租户表获取对应主键ID,写入缓存后返回,缓存过期时间可设置为12~24小时,租户信息变更时主动失效对应缓存即可

全局设置会话上下文实现(EF Core拦截器)

无需在业务代码中重复编写逻辑,通过拦截器全局自动注入会话上下文:

  1. 注册IHttpContextAccessor用于获取当前请求的租户信息
services.AddHttpContextAccessor();
  1. 实现自定义EF Core命令拦截器
public class TenantSessionContextInterceptor : IDbCommandInterceptor
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private const string TenantSessionKey = "TenantId";

    public TenantSessionContextInterceptor(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        AppendSessionContext(command);
        return base.ReaderExecuting(command, eventData, result);
    }

    public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
    {
        AppendSessionContext(command);
        return base.NonQueryExecuting(command, eventData, result);
    }

    public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
    {
        AppendSessionContext(command);
        return base.ScalarExecuting(command, eventData, result);
    }

    private void AppendSessionContext(DbCommand command)
    {
        var context = _httpContextAccessor.HttpContext;
        if (context == null || !context.Items.TryGetValue("CurrentTenantId", out var tenantIdObj) || tenantIdObj is not int tenantId)
        {
            throw new UnauthorizedAccessException("缺少有效租户信息");
        }

        var setCmd = command.CreateCommand();
        setCmd.CommandText = $"EXEC sp_set_session_context @key=N'{TenantSessionKey}', @value=@TenantId";
        setCmd.Parameters.Add(new SqlParameter("@TenantId", SqlDbType.Int) { Value = tenantId });
        setCmd.ExecuteNonQuery();
    }
}
  1. 注册拦截器到DbContext
services.AddDbContext<YourDbContext>((sp, options) =>
{
    options.UseSqlServer("Azure SQL连接字符串")
           .AddInterceptors(sp.GetRequiredService<TenantSessionContextInterceptor>());
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 00:24:00