如何扩展EF Core全局查询过滤器以支持多租户集合实体
适配EF Core全局查询过滤器以支持多租户集合场景
我正在基于Entity Framework Core为ASP.NET Core应用实现多租户功能。目前已完成单租户实体(拥有TenantId字段)的全局查询过滤器配置,手动配置代码如下:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Company>(builder => { builder.HasIndex(c => c.TenantId); builder.HasQueryFilter(c => c.TenantId == _tenantId); }); modelBuilder.Entity<Sale>(builder => { builder.HasIndex(s => s.TenantId); builder.HasQueryFilter(s => s.TenantId == _tenantId); }); }
这段逻辑已通过扩展方法简化为:
modelBuilder.SetQueryFilterOnAllEntities<ITenantEntity>(x => x.TenantId == _tenantId);
但部分实体(如Deal和FinanceAgreement)属于多租户,通过Tenants集合关联,目前只能手动配置过滤器:
modelBuilder.Entity<Deal>(builder => { builder.HasQueryFilter(x => x.Tenants.Any(le => le.Id == _tenantId)); }); modelBuilder.Entity<FinanceAgreement>(builder => { builder.HasQueryFilter(x => x.Tenants.Any(le => le.Id == _tenantId)); });
如何修改现有扩展方法,使其同时支持单TenantId和多租户集合两种场景?
现有扩展方法代码
public static class ModelBuilderExtensions { private static readonly MethodInfo SetQueryFilterMethod = typeof(ModelBuilderExtensions) .GetMethods(BindingFlags.NonPublic | BindingFlags.Static) .Single(t => t is { IsGenericMethod: true, Name: nameof(SetQueryFilter) }); public static void SetQueryFilterOnAllEntities<TEntityInterface>( this ModelBuilder builder, Expression<Func<TEntityInterface, bool>> filterExpression) { var entityTypes = builder.Model.GetEntityTypes() .Where(t => t.BaseType == null) .Select(t => t.ClrType) .Where(t => typeof(TEntityInterface).IsAssignableFrom(t)); foreach (var type in entityTypes) { builder.SetEntityQueryFilter(type, filterExpression); } } private static void SetEntityQueryFilter<TEntityInterface>( this ModelBuilder builder, Type entityType, Expression<Func<TEntityInterface, bool>> filterExpression) => SetQueryFilterMethod .MakeGenericMethod(entityType, typeof(TEntityInterface)) .Invoke(null, [builder, filterExpression]); private static void SetQueryFilter<TEntity, TEntityInterface>( this ModelBuilder builder, Expression<Func<TEntityInterface, bool>> filterExpression) where TEntityInterface : class where TEntity : class, TEntityInterface { var concreteExpression = filterExpression .Convert<TEntityInterface, TEntity>(); builder.Entity<TEntity>() .AppendQueryFilter(concreteExpression); } // CREDIT: magiak on GitHub private static void AppendQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression) where T : class { var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType); var expressionFilter = ReplacingExpressionVisitor.Replace(expression.Parameters.Single(), parameterType, expression.Body); var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter(); if (currentQueryFilter is not null) { var currentExpressionFilter = ReplacingExpressionVisitor.Replace(currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body); expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter); } var lambdaExpression = Expression.Lambda(expressionFilter, parameterType); entityTypeBuilder.HasQueryFilter(lambdaExpression); } } public static class ExpressionExtensions { // 适配类型转换的表达式访问器 public static Expression<Func<TTarget, bool>> Convert<TSource, TTarget>(this Expression<Func<TSource, bool>> root) { var visitor = new ParameterTypeVisitor<TSource, TTarget>(); return (Expression<Func<TTarget, bool>>)visitor.Visit(root); } private class ParameterTypeVisitor<TSource, TTarget> : ExpressionVisitor { private ReadOnlyCollection<ParameterExpression>? _parameters; protected override Expression VisitParameter(ParameterExpression node) => _parameters?.FirstOrDefault(p => p.Name == node.Name) ?? (node.Type == typeof(TSource) ? Expression.Parameter(typeof(TTarget), node.Name) : node); protected override Expression VisitLambda<T>(Expression<T> node) { _parameters = VisitAndConvert(node.Parameters, "VisitLambda"); return Expression.Lambda(Visit(node.Body), _parameters); } } }
解决方案:扩展支持多租户集合场景
1. 定义多租户集合接口
首先为多租户集合实体定义统一接口:
public interface IMultiTenantEntity { ICollection<Tenant> Tenants { get; set; } } // 假设Tenant实体定义如下 public class Tenant { public int Id { get; set; } // 其他租户属性 }
2. 修改扩展方法,添加多租户集合支持
在ModelBuilderExtensions中新增针对多租户集合实体的扩展方法,复用现有核心逻辑:
public static class ModelBuilderExtensions { private static readonly MethodInfo SetQueryFilterMethod = typeof(ModelBuilderExtensions) .GetMethods(BindingFlags.NonPublic | BindingFlags.Static) .Single(t => t is { IsGenericMethod: true, Name: nameof(SetQueryFilter) }); // 原有单租户实体扩展方法保持不变 public static void SetQueryFilterOnAllEntities<TEntityInterface>( this ModelBuilder builder, Expression<Func<TEntityInterface, bool>> filterExpression) { var entityTypes = builder.Model.GetEntityTypes() .Where(t => t.BaseType == null) .Select(t => t.ClrType) .Where(t => typeof(TEntityInterface).IsAssignableFrom(t)); foreach (var type in entityTypes) { builder.SetEntityQueryFilter(type, filterExpression); } } // 新增:针对多租户集合实体的扩展方法 public static void SetMultiTenantCollectionFilterOnAllEntities<TMultiTenantInterface>( this ModelBuilder builder, Expression<Func<TMultiTenantInterface, bool>> filterExpression) where TMultiTenantInterface : class, IMultiTenantEntity { var entityTypes = builder.Model.GetEntityTypes() .Where(t => t.BaseType == null) .Select(t => t.ClrType) .Where(t => typeof(TMultiTenantInterface).IsAssignableFrom(t)); foreach (var type in entityTypes) { builder.SetEntityQueryFilter(type, filterExpression); } } private static void SetEntityQueryFilter<TEntityInterface>( this ModelBuilder builder, Type entityType, Expression<Func<TEntityInterface, bool>> filterExpression) => SetQueryFilterMethod .MakeGenericMethod(entityType, typeof(TEntityInterface)) .Invoke(null, [builder, filterExpression]); private static void SetQueryFilter<TEntity, TEntityInterface>( this ModelBuilder builder, Expression<Func<TEntityInterface, bool>> filterExpression) where TEntityInterface : class where TEntity : class, TEntityInterface { var concreteExpression = filterExpression .Convert<TEntityInterface, TEntity>(); builder.Entity<TEntity>() .AppendQueryFilter(concreteExpression); } // AppendQueryFilter方法保持不变 private static void AppendQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> expression) where T : class { var parameterType = Expression.Parameter(entityTypeBuilder.Metadata.ClrType); var expressionFilter = ReplacingExpressionVisitor.Replace(expression.Parameters.Single(), parameterType, expression.Body); var currentQueryFilter = entityTypeBuilder.Metadata.GetQueryFilter(); if (currentQueryFilter is not null) { var currentExpressionFilter = ReplacingExpressionVisitor.Replace(currentQueryFilter.Parameters.Single(), parameterType, currentQueryFilter.Body); expressionFilter = Expression.AndAlso(currentExpressionFilter, expressionFilter); } var lambdaExpression = Expression.Lambda(expressionFilter, parameterType); entityTypeBuilder.HasQueryFilter(lambdaExpression); } }
3. 使用方式
在OnModelCreating中同时调用两个扩展方法,分别处理单租户和多租户集合实体:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 为单租户实体配置全局过滤器 modelBuilder.SetQueryFilterOnAllEntities<ITenantEntity>(x => x.TenantId == _tenantId); // 为多租户集合实体配置全局过滤器 modelBuilder.SetMultiTenantCollectionFilterOnAllEntities<IMultiTenantEntity>(x => x.Tenants.Any(le => le.Id == _tenantId)); }
关键说明
- 新增的
SetMultiTenantCollectionFilterOnAllEntities方法复用了原有核心逻辑,仅针对多租户集合接口进行筛选 - 保持原有代码兼容性,无需修改单租户实体的配置方式
- 表达式转换逻辑
Convert方法可自动适配多租户接口到具体实体类型
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

