基于泛型的LINQ动态表达式过滤管理器返回结果为空问题排查
问题根源分析
你的过滤结果为空的核心问题在于调用Compare方法时传入的selector表达式是反射方法调用,而非直接的属性访问表达式,这导致表达式树无法正确解析属性值的类型匹配,最终比较逻辑失效。
具体来说,你当前的selector是:
x => x.GetType().GetProperty(filter.ColumnProperty).GetValue(x)
这本质是在表达式树中嵌入了GetType()、GetProperty()、GetValue()三个反射方法的调用,而非直接生成x.Year这样的属性访问节点。当LINQ执行这个表达式时,会存在以下问题:
GetValue()返回object类型,导致Compare方法的TValue被推断为object,而你的filter.ColumnValue如果是值类型(比如int),会被装箱,虽然值相等,但表达式树的类型匹配逻辑可能出现隐性问题;- 反射调用的表达式无法被LINQ提供者(即使是内存中的LINQ to Objects)优化,且容易出现类型转换的隐性错误;
- 若
filter.ColumnProperty拼写错误,GetProperty()返回null,后续GetValue()会直接抛出空引用异常。
修复方案
需要动态构建属性访问的表达式树,替代反射方法调用。具体步骤如下:
1. 扩展Compare方法,添加动态属性版本
public static IQueryable<TSource> Compare<TSource>( this IQueryable<TSource> source, string propertyName, object value, ExpressionType comparison) { // 构建参数表达式:x => x var parameter = Expression.Parameter(typeof(TSource), "x"); // 构建属性访问表达式:x.PropertyName var property = Expression.Property(parameter, propertyName); // 将值转换为属性的类型(处理装箱/拆箱) var convertedValue = Expression.Constant(Convert.ChangeType(value, property.Type), property.Type); BinaryExpression body; switch (comparison) { case ExpressionType.LessThan: body = Expression.LessThan(property, convertedValue); break; case ExpressionType.LessThanOrEqual: body = Expression.LessThanOrEqual(property, convertedValue); break; case ExpressionType.Equal: body = Expression.Equal(property, convertedValue); break; case ExpressionType.NotEqual: body = Expression.NotEqual(property, convertedValue); break; case ExpressionType.GreaterThan: body = Expression.GreaterThan(property, convertedValue); break; case ExpressionType.GreaterThanOrEqual: body = Expression.GreaterThanOrEqual(property, convertedValue); break; case ExpressionType.IsTrue: // 处理"is empty"逻辑:检查属性是否为null或默认值 body = Expression.Equal(property, Expression.Default(property.Type)); break; case ExpressionType.IsFalse: // 处理"is not empty"逻辑:检查属性是否不为null且不是默认值 body = Expression.NotEqual(property, Expression.Default(property.Type)); break; default: throw new NotSupportedException($"Comparison operator {comparison} is not supported"); } // 构建Lambda表达式并应用Where过滤 var lambda = Expression.Lambda<Func<TSource, bool>>(body, parameter); return source.Where(lambda); }
2. 修改Filter方法,使用新的Compare重载
public Task<IEnumerable<T>> Filter<T>(IList<DataTableFilter> filters, IEnumerable<T> filterables) { if (filters == null || filters.Count == 0) return Task.FromResult(filterables); var queryable = filterables.AsQueryable(); foreach (var filter in filters) { if (string.IsNullOrEmpty(filter.ColumnProperty)) continue; queryable = queryable.Compare(filter.ColumnProperty, filter.ColumnValue, filter.FilterOperator); } return Task.FromResult(queryable.AsEnumerable()); }
3. 优化DataTableFilter的FilterOperator属性
补充is empty和is not empty的逻辑适配:
public record DataTableFilter { public string? ColumnProperty { get; set; } public object? ColumnValue { get; set; } public string? Operator { get; set; } public ExpressionType FilterOperator { get { return Operator switch { "=" => ExpressionType.Equal, "!=" => ExpressionType.NotEqual, ">" => ExpressionType.GreaterThan, ">=" => ExpressionType.GreaterThanOrEqual, "<" => ExpressionType.LessThan, "<=" => ExpressionType.LessThanOrEqual, "is empty" => ExpressionType.IsTrue, "is not empty" => ExpressionType.IsFalse, _ => ExpressionType.Throw }; } } }
测试验证
修改后,当你测试Year=2024的过滤条件时,表达式树会直接生成x => x.Year == 2024这样的逻辑,类型匹配正确,过滤结果会符合预期。
内容的提问来源于stack exchange,提问作者B-M
相关产品推荐
相关产品推荐

