如何基于筛选对象自动实现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);
额外说明
- 缓存优化:通过静态字典缓存实体与筛选类的属性映射,避免每次调用都反射,提升性能。
- 容错处理:如果筛选类的属性找不到对应的实体属性,会自动忽略该筛选条件,不会抛出异常。
- 类型安全:仅当筛选集合的元素类型与实体属性类型完全匹配时,才会应用筛选条件,避免类型错误。
内容的提问来源于stack exchange,提问作者KorsaR
相关产品推荐
相关产品推荐

