多租户WebApp中DbContext访问与全局查询过滤实现问题
多租户WebApp权限过滤解决方案
问题1:CheckifCanView方法中获取TenantId和LoggedInUserId
实体类(BaseItem)不应直接依赖DbContext,这会破坏实体的单一职责。正确的实现方式是:
- 不要让
CheckifCanView主动获取用户/租户ID,而是将当前登录用户ID(LoggedInUserId)和当前租户ID(CurrentTenantId)作为参数传入该方法。 - 由于你的DbContext是运行时动态配置的,可以在创建DbContext实例时,通过构造函数注入或专用方法,将当前用户和租户ID存入DbContext的私有字段。后续调用
CheckifCanView时,从DbContext中取出这两个值传入即可。
示例代码:
// ApplicationDbContext中存储当前上下文信息 public class ApplicationDbContext : DbContext { private readonly Guid _currentTenantId; private readonly Guid _currentUserId; // 构造函数传入用户/租户ID(可从HttpContextAccessor获取后传入) public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, Guid currentTenantId, Guid currentUserId) : base(options) { _currentTenantId = currentTenantId; _currentUserId = currentUserId; } // 封装CheckifCanView调用,传入所需参数 public bool CanViewItem(BaseItem item) { return item.CheckifCanView(_currentUserId, _currentTenantId); } } // BaseItem中的CheckifCanView实现 public class BaseItem { public SharedWith SharedWith { get; set; } public Guid TenantId { get; set; } public Guid CreatedByUserId { get; set; } [NotMapped] public bool CanView { get; set; } public bool CheckifCanView(Guid loggedInUserId, Guid currentTenantId) { return SharedWith switch { SharedWith.Public => true, SharedWith.Private => loggedInUserId == CreatedByUserId, SharedWith.Tenant => currentTenantId == TenantId, SharedWith.Archive => false, // 按需求调整归档项的可见性 _ => false }; } }
问题2:为所有BaseItem子类实现全局查询过滤
CanView是未映射属性,EF Core无法将其转换为SQL,因此不能直接用于查询过滤。需要将CanView的判断逻辑转换为EF Core可解析的表达式树,然后批量为所有BaseItem的子类添加查询过滤:
步骤1:定义通用过滤表达式
// 生成对应权限逻辑的查询表达式 public static Expression<Func<T, bool>> GetBaseItemFilter<T>(Guid currentUserId, Guid currentTenantId) where T : BaseItem { return item => item.SharedWith == SharedWith.Public || (item.SharedWith == SharedWith.Private && item.CreatedByUserId == currentUserId) || (item.SharedWith == SharedWith.Tenant && item.TenantId == currentTenantId) && item.SharedWith != SharedWith.Archive; // 排除归档项 }
步骤2:批量为所有子类添加过滤
在DbContext的OnModelCreating方法中,遍历所有继承自BaseItem的实体类型,动态添加查询过滤:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 遍历所有BaseItem的子类实体 foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { if (typeof(BaseItem).IsAssignableFrom(entityType.ClrType)) { // 构造当前实体类型的过滤表达式 var filterMethod = typeof(ApplicationDbContext) .GetMethod(nameof(GetBaseItemFilter), BindingFlags.Public | BindingFlags.Static)! .MakeGenericMethod(entityType.ClrType); var filterExpression = (LambdaExpression)filterMethod.Invoke(null, new object[] { _currentUserId, _currentTenantId })!; // 为实体添加全局查询过滤 modelBuilder.Entity(entityType.ClrType).HasQueryFilter(filterExpression); } } // 其他模型配置代码... }
处理动态用户/租户ID
如果用户和租户ID是每个请求动态变化的(而非DbContext初始化时固定),需要使用可参数化的查询过滤(EF Core 5+支持):
// 在DbContext中定义可动态更新的参数 public Guid CurrentUserId { get; set; } public Guid CurrentTenantId { get; set; } // 调整过滤表达式,引用DbContext的属性 public static Expression<Func<T, bool>> GetBaseItemFilter<T>() where T : BaseItem { return item => item.SharedWith == SharedWith.Public || (item.SharedWith == SharedWith.Private && item.CreatedByUserId == EF.Property<Guid>(item, nameof(CurrentUserId))) || (item.SharedWith == SharedWith.Tenant && item.TenantId == EF.Property<Guid>(item, nameof(CurrentTenantId))) && item.SharedWith != SharedWith.Archive; } // OnModelCreating中使用无参数的过滤表达式 protected override void OnModelCreating(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { if (typeof(BaseItem).IsAssignableFrom(entityType.ClrType)) { var filterMethod = typeof(ApplicationDbContext) .GetMethod(nameof(GetBaseItemFilter), BindingFlags.Public | BindingFlags.Static)! .MakeGenericMethod(entityType.ClrType); var filterExpression = (LambdaExpression)filterMethod.Invoke(null, null)!; modelBuilder.Entity(entityType.ClrType).HasQueryFilter(filterExpression); } } } // 提供方法在请求开始时设置当前用户上下文 public void SetCurrentContext(Guid userId, Guid tenantId) { CurrentUserId = userId; CurrentTenantId = tenantId; }
在请求中间件中调用SetCurrentContext方法,传入当前用户和租户ID即可。
内容的提问来源于stack exchange,提问作者Pierre
相关产品推荐
相关产品推荐

