EF中基于运行时评估的条件式多租户全局过滤实现问询
问题描述
我正在构建一个DbContext基类,要自动实现多租户感知、审计日志等功能。团队继承这个基类并添加实体后,只需要少量手动操作就能获得具备多租户强制管控能力的数据库上下文,以此减少30余个独立DbContext服务的改造工作量和定制成本。
我定义了如下接口ITenantAwareEntity:
public interface ITenantAwareEntity { /// <summary> /// Specifies the customer that this record belongs to. /// </summary> Guid? CustomerId { get; set; } }
我已经实现了SaveChangesInterceptor,通过注入的IContextProvider(提供当前用户、当前租户等上下文信息)确保保存前CustomerId始终被填充。
在查询层面,我想借助IContextProvider实现基于CustomerId的多租户全局过滤,但现有示例和我的实现差异较大,Lambda表达式的编写让我很困惑。
注入到DbContext中的IContextProvider包含IsServiceContext属性,用来标识当前使用DbContext的是服务账号(无需应用全局过滤,因为要管理多租户记录)还是普通租户。
我需要编写满足以下逻辑的Lambda表达式:
- 若当前为服务账号(根据
IContextProvider判断),则不应用过滤; - 否则,根据
IContextProvider中的CustomerId过滤结果集。
请问如何编写该表达式,使其能应用于所有实现ITenantAwareEntity的实体,并在每次数据库操作时进行运行时动态评估?
解决方案
1. 在DbContext基类中注入IContextProvider
首先确保DbContext基类持有IContextProvider实例,后续过滤逻辑依赖它获取当前上下文信息:
public abstract class TenantDbContext : DbContext { protected readonly IContextProvider _contextProvider; public TenantDbContext(DbContextOptions options, IContextProvider contextProvider) : base(options) { _contextProvider = contextProvider; } }
2. 编写动态过滤表达式生成方法
通过表达式树动态构建符合要求的Lambda表达式,确保EF Core能正确解析并转换为SQL:
protected Expression<Func<TEntity, bool>> GetTenantFilter<TEntity>() where TEntity : class, ITenantAwareEntity { // 服务账号直接返回无过滤逻辑 if (_contextProvider.IsServiceContext) { return entity => true; } var currentCustomerId = _contextProvider.CustomerId; // 构建过滤表达式:entity.CustomerId == 当前租户ID var parameter = Expression.Parameter(typeof(TEntity), "entity"); var customerIdProperty = Expression.Property(parameter, nameof(ITenantAwareEntity.CustomerId)); var constantValue = Expression.Constant(currentCustomerId, typeof(Guid?)); var equalExpression = Expression.Equal(customerIdProperty, constantValue); return Expression.Lambda<Func<TEntity, bool>>(equalExpression, parameter); }
3. 在OnModelCreating中全局应用过滤
遍历所有实现ITenantAwareEntity的实体类型,为它们自动添加全局查询过滤:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 筛选所有实现ITenantAwareEntity的实体 var tenantEntities = modelBuilder.Model.GetEntityTypes() .Where(t => typeof(ITenantAwareEntity).IsAssignableFrom(t.ClrType)); foreach (var entityType in tenantEntities) { // 动态调用GetTenantFilter生成对应实体的过滤表达式 var filterMethod = typeof(TenantDbContext) .GetMethod(nameof(GetTenantFilter), BindingFlags.Instance | BindingFlags.NonPublic)! .MakeGenericMethod(entityType.ClrType); var filterExpression = filterMethod.Invoke(this, null) as LambdaExpression; // 为实体添加全局查询过滤 modelBuilder.Entity(entityType.ClrType).HasQueryFilter(filterExpression); } }
关键说明
- 运行时动态评估:
IContextProvider的IsServiceContext和CustomerId是运行时变量,EF Core会在每次查询时重新评估表达式,确保过滤逻辑始终匹配当前上下文。 - 服务账号豁免:当
IsServiceContext为true时,返回的entity => true表达式等价于无过滤,服务账号可访问所有租户数据。 - 类型安全:通过泛型约束确保过滤逻辑仅对实现
ITenantAwareEntity的实体生效。
内容的提问来源于stack exchange,提问作者user610217
相关产品推荐
相关产品推荐

