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

如何扩展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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:34:51