Linq技术咨询:如何结合反射实现含Contains的动态Where查询
动态Contains筛选的实现方案
我明白你想把硬编码的Description属性筛选逻辑改成动态可配置的,这样就能灵活针对任意字符串属性执行Contains过滤了。下面是基于表达式树的最佳实践方案——它既能被EF Core正确解析为SQL查询,又能保持代码的可扩展性:
完整修改后的代码
using System.Linq.Expressions; using System.Globalization; public List<TEntity> GetItems(string sortBy, string sort, string? filter, string filterPropertyName = "Description") { IQueryable<TEntity> items = dbContext.Set<TEntity>(); if (!string.IsNullOrEmpty(filter)) { string lowerFilter = filter.ToLower(CultureInfo.CurrentCulture); var filterExpression = BuildContainsFilterExpression<TEntity>(filterPropertyName, lowerFilter); items = items.Where(filterExpression); } if (!string.IsNullOrEmpty(sortBy)) { items = items.OrderBy($"{sortBy} {sort}"); } return items.ToList(); } // 动态构建Contains筛选的表达式树 private Expression<Func<TEntity, bool>> BuildContainsFilterExpression<T>(string propertyName, string lowerFilter) { // 参数表达式:p => ... var parameter = Expression.Parameter(typeof(T), "p"); // 获取属性:p.[指定属性名] var property = Expression.Property(parameter, propertyName); // 处理null检查:p.[指定属性名] != null var nullCheck = Expression.NotEqual(property, Expression.Constant(null, typeof(string))); // 调用ToLower():p.[指定属性名].ToLower(当前文化) var toLowerMethod = typeof(string).GetMethod("ToLower", new[] { typeof(CultureInfo) })!; var toLowerCall = Expression.Call( property, toLowerMethod, Expression.Constant(CultureInfo.CurrentCulture) ); // 调用Contains():p.[指定属性名].ToLower().Contains(过滤值) var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) })!; var containsCall = Expression.Call( toLowerCall, containsMethod, Expression.Constant(lowerFilter, typeof(string)) ); // 组合条件:null检查 && Contains匹配 var combinedCondition = Expression.AndAlso(nullCheck, containsCall); // 构建最终表达式并返回 return Expression.Lambda<Func<T, bool>>(combinedCondition, parameter); }
关键细节解释
- 表达式树的必要性:EF Core需要解析表达式树来生成对应的SQL语句,如果直接用反射调用方法(比如
property.GetValue(p).ToString().Contains(...)),会导致内存中过滤(客户端评估),性能极差。表达式树能让EF Core把整个筛选逻辑翻译成SQL,在数据库层面执行。 - 属性合法性校验:如果需要更严谨,可以在
BuildContainsFilterExpression方法开头加属性存在性检查,避免传入不存在的属性名:if (!typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance)?.PropertyType.IsAssignableTo(typeof(string)) ?? false) { throw new ArgumentException($"类型{typeof(T).Name}不存在可访问的字符串属性{propertyName}"); } - 灵活性扩展:你可以进一步封装,比如支持传入多个属性名做OR筛选,或者让用户指定是否忽略大小写(不过你的当前逻辑已经处理了忽略大小写)。
使用示例
- 默认筛选
Description属性:var items = GetItems("Id", "ASC", "test filter"); - 动态指定其他属性(比如
Name):var items = GetItems("Id", "ASC", "test filter", "Name");
内容的提问来源于stack exchange,提问作者Henning
相关产品推荐
相关产品推荐

