如何通过Lambda表达式实现IQueryable多列筛选 无需为各场景写独立if语句
IQueryable多列动态筛选最优实现方案
你原有实现的核心问题是硬编码枚举所有筛选组合,扩展性极差,还存在逻辑歧义:当SearchColumns同时包含warehouse和date时,前面两个if会先拼接两个AND条件,再叠加第三个if的多列OR条件,最终逻辑和预期的多列模糊匹配大概率不符。
推荐两种落地成本低的优化方案:
方案1:轻量映射方案(适配当前业务场景,无额外依赖)
核心思路是提前定义列名和筛选逻辑的映射关系,动态选中需要的筛选条件后拼接为OR查询,无需写大量if判断。
实现代码
if (!string.IsNullOrWhiteSpace(orderFilter.SearchValue)) { // 1. 预设列名和对应筛选逻辑的映射 var filterMap = new Dictionary<string, Expression<Func<Order, bool>>> { ["warehouse"] = x => x.Warehouse.Description.Contains(orderFilter.SearchValue), ["date"] = x => x.Date.ToString().Contains(orderFilter.SearchValue), ["orderno"] = x => x.OrderNo.Contains(orderFilter.SearchValue), ["client"] = x => x.Client.Description.Contains(orderFilter.SearchValue), ["salesman"] = x => x.Salesman.Username.Contains(orderFilter.SearchValue) }; // 2. 取出选中列对应的筛选表达式 var selectedFilters = orderFilter.SearchColumns .Where(col => filterMap.ContainsKey(col)) .Select(col => filterMap[col]) .ToList(); if (selectedFilters.Any()) { // 3. 把多个筛选表达式拼接为OR条件 var parameter = Expression.Parameter(typeof(Order), "x"); var combinedBody = selectedFilters .Select(f => Expression.Invoke(f, parameter)) .Aggregate(Expression.OrElse); var combinedLambda = Expression.Lambda<Func<Order, bool>>(combinedBody, parameter); // 4. 拼接查询条件 query = query.Where(combinedLambda); } }
优势
- 改造成本极低,直接替换原有if逻辑即可
- 新增筛选列只需要在
filterMap里加一行配置,无需修改逻辑代码 - 完全兼容EF/EF Core的SQL转换,没有性能问题
方案2:通用表达式树方案(支持任意实体复用)
如果需要在多个实体的筛选场景复用逻辑,可以封装通用的多列筛选扩展方法,无需针对每个实体写映射:
实现代码
public static class QueryableExtensions { /// <summary> /// IQueryable多列模糊匹配扩展 /// </summary> /// <typeparam name="T">实体类型</typeparam> /// <param name="query">原查询对象</param> /// <param name="searchValue">搜索值</param> /// <param name="propertyPaths">要筛选的属性路径,支持嵌套属性如"Warehouse.Description"</param> public static IQueryable<T> WhereMultiColumnContains<T>(this IQueryable<T> query, string searchValue, params string[] propertyPaths) { if (string.IsNullOrWhiteSpace(searchValue) || propertyPaths?.Any() != true) return query; var parameter = Expression.Parameter(typeof(T), "x"); Expression combinedBody = null; var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); foreach (var path in propertyPaths) { // 解析嵌套属性 Expression property = parameter; foreach (var propName in path.Split('.')) { property = Expression.Property(property, propName); } // 值类型转字符串 if (property.Type != typeof(string)) { var toStringMethod = property.Type.GetMethod("ToString", Type.EmptyTypes); property = Expression.Call(property, toStringMethod); } // 拼接Contains条件 var containsExpr = Expression.Call(property, containsMethod, Expression.Constant(searchValue)); combinedBody = combinedBody == null ? containsExpr : Expression.OrElse(combinedBody, containsExpr); } var lambda = Expression.Lambda<Func<T, bool>>(combinedBody, parameter); return query.Where(lambda); } }
使用示例
if (!string.IsNullOrWhiteSpace(orderFilter.SearchValue)) { // 把列名转成对应的属性路径即可,列名和属性名不一致可以做一层简单映射 var propertyPaths = orderFilter.SearchColumns.Select(col => col switch { "warehouse" => "Warehouse.Description", "date" => "Date", "orderno" => "OrderNo", "client" => "Client.Description", "salesman" => "Salesman.Username", _ => null }).Where(p => p != null).ToArray(); query = query.WhereMultiColumnContains(orderFilter.SearchValue, propertyPaths); }
优势
- 一次封装,所有实体的多列筛选场景都可以复用
- 支持嵌套属性、值类型自动转字符串,适配绝大多数模糊搜索场景
注意事项
- 如果使用EF/EF Core,避免在筛选逻辑中调用自定义方法,否则会触发客户端计算,影响性能
- 大小写敏感问题可以统一将属性值转小写后再匹配,和你现有
SearchValue转小写的逻辑对齐 - 针对DateTime类型的格式化匹配,建议用
DbFunctions类的方法代替直接ToString,兼容性更好
内容的提问来源于stack exchange,提问作者Cll
相关产品推荐
相关产品推荐

