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

如何基于筛选对象自动实现EF查询筛选?避免冗余重复代码

通用IQueryable筛选实现方案

针对大量实体与筛选类的重复筛选代码问题,可以通过表达式树+约定配置的方式实现通用筛选逻辑,彻底告别冗余的if-Where代码。

核心约定

约定筛选类的集合属性名与实体类的单个属性名遵循复数对应关系:

  • 实体属性Id → 筛选类集合属性Ids
  • 实体属性Name → 筛选类集合属性Names

通用扩展方法实现

using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public static class QueryableFilterExtensions
{
    // 缓存实体与筛选类的属性映射,避免重复反射
    private static readonly Dictionary<(Type EntityType, Type FilterType), Dictionary<PropertyInfo, PropertyInfo>> _propertyMappings = new();

    public static IQueryable<TEntity> ApplyFilter<TEntity, TFilter>(this IQueryable<TEntity> query, TFilter filter)
        where TEntity : class
        where TFilter : class
    {
        if (filter == null) return query;

        var entityType = typeof(TEntity);
        var filterType = typeof(TFilter);
        var propertyMap = GetPropertyMapping(entityType, filterType);

        foreach (var (filterProp, entityProp) in propertyMap)
        {
            var filterValue = filterProp.GetValue(filter) as ICollection;
            // 仅当筛选集合非空时应用条件
            if (filterValue?.Count > 0)
            {
                // 构建表达式:q => filter.{FilterProp}.Contains(q.{EntityProp})
                var param = Expression.Parameter(entityType, "q");
                var entityPropertyExpr = Expression.Property(param, entityProp);
                var filterValueExpr = Expression.Constant(filterValue);

                // 获取Enumerable.Contains方法(适配泛型)
                var containsMethod = typeof(Enumerable)
                    .GetMethod("Contains", new[] { typeof(IEnumerable<>).MakeGenericType(entityProp.PropertyType), entityProp.PropertyType })!;

                var containsExpr = Expression.Call(filterValueExpr, containsMethod, entityPropertyExpr);
                var lambda = Expression.Lambda<Func<TEntity, bool>>(containsExpr, param);

                query = query.Where(lambda);
            }
        }

        return query;
    }

    private static Dictionary<PropertyInfo, PropertyInfo> GetPropertyMapping(Type entityType, Type filterType)
    {
        var key = (entityType, filterType);
        if (_propertyMappings.TryGetValue(key, out var mapping))
        {
            return mapping;
        }

        mapping = new Dictionary<PropertyInfo, PropertyInfo>();
        var filterProps = filterType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => typeof(ICollection).IsAssignableFrom(p.PropertyType));

        foreach (var filterProp in filterProps)
        {
            // 从筛选属性名去掉末尾's'匹配实体属性
            var entityPropName = filterProp.Name.TrimEnd('s');
            var entityProp = entityType.GetProperty(entityPropName, BindingFlags.Public | BindingFlags.Instance);
            if (entityProp != null && entityProp.PropertyType == GetGenericItemType(filterProp.PropertyType))
            {
                mapping.Add(filterProp, entityProp);
            }
        }

        _propertyMappings[key] = mapping;
        return mapping;
    }

    // 获取泛型集合的元素类型
    private static Type GetGenericItemType(Type collectionType)
    {
        return collectionType.GetInterfaces()
            .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(i => i.GetGenericArguments()[0])
            .FirstOrDefault() ?? collectionType;
    }
}

使用方式

只需一行代码替代所有冗余的筛选逻辑:

// 原冗余代码
// if(filter.Ids.Any()) query = query.Where(q=>filter.Ids.Contains(q.Id));
// if(filter.Names.Any()) query = query.Where(q=>filter.Names.Contains(q.Name));

// 通用实现
query = query.ApplyFilter(filter);

额外说明

  1. 缓存优化:通过静态字典缓存实体与筛选类的属性映射,避免每次调用都反射,提升性能。
  2. 容错处理:如果筛选类的属性找不到对应的实体属性,会自动忽略该筛选条件,不会抛出异常。
  3. 类型安全:仅当筛选集合的元素类型与实体属性类型完全匹配时,才会应用筛选条件,避免类型错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:35:15