在数据处理场景中,动态过滤需求十分常见,比如用户可以在界面上自定义筛选条件,组合多个字段的匹配规则。利用Lambda表达式构建谓词过滤引擎,能够让过滤逻辑动态生成,同时保证较高的执行性能,适配动态变量的灵活变化。

核心设计思路
谓词过滤引擎的核心是将用户传入的动态过滤条件,转换为可执行的Lambda表达式树,最终编译为委托直接对数据集合进行筛选。整个流程分为三个核心步骤:动态变量解析、谓词表达式构建、表达式编译与执行。
动态变量解析
动态变量通常来自用户的配置输入,比如字段名、比较运算符、目标值,我们需要先将这些配置映射为对应的属性访问表达式和常量表达式。假设我们处理的数据实体为User类,定义如下:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Role { get; set; }
}
解析动态变量时,首先通过反射获取目标类型的属性信息,再构建属性访问的表达式节点:
using System;
using System.Linq.Expressions;
using System.Reflection;
public class DynamicVariableParser
{
// 解析属性访问表达式
public static MemberExpression ParsePropertyAccess<T>(string propertyName)
{
// 获取T类型的属性信息
PropertyInfo property = typeof(T).GetProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"类型{typeof(T).Name}不存在属性{propertyName}");
}
// 构建参数表达式,代表被过滤的实体对象
ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
// 构建属性访问表达式 x.PropertyName
MemberExpression propertyAccess = Expression.Property(parameter, property);
return propertyAccess;
}
}
谓词表达式构建
根据解析后的属性和用户选择的比较运算符,构建对应的二元表达式,最终组合为完整的Lambda表达式。常见的比较运算符包括等于、不等于、大于、小于、包含等,我们可以通过一个方法统一处理:
public class PredicateBuilder
{
// 构建单个条件的谓词表达式
public static Expression<Func<T, bool>> BuildSinglePredicate<T>(string propertyName, string operatorStr, object targetValue)
{
// 获取属性访问表达式和参数表达式
MemberExpression propertyAccess = DynamicVariableParser.ParsePropertyAccess<T>(propertyName);
ParameterExpression parameter = (ParameterExpression)propertyAccess.Expression;
// 将目标值转换为属性对应的类型,构建常量表达式
Type propertyType = propertyAccess.Type;
object convertedValue = Convert.ChangeType(targetValue, propertyType);
ConstantExpression constant = Expression.Constant(convertedValue, propertyType);
// 根据运算符构建二元表达式
BinaryExpression binaryExpression;
switch (operatorStr.ToLower())
{
case "eq": // 等于
binaryExpression = Expression.Equal(propertyAccess, constant);
break;
case "neq": // 不等于
binaryExpression = Expression.NotEqual(propertyAccess, constant);
break;
case "gt": // 大于
binaryExpression = Expression.GreaterThan(propertyAccess, constant);
break;
case "lt": // 小于
binaryExpression = Expression.LessThan(propertyAccess, constant);
break;
case "gte": // 大于等于
binaryExpression = Expression.GreaterThanOrEqual(propertyAccess, constant);
break;
case "lte": // 小于等于
binaryExpression = Expression.LessThanOrEqual(propertyAccess, constant);
break;
default:
throw new ArgumentException($"不支持的运算符:{operatorStr}");
}
// 构建Lambda表达式 x => 二元表达式
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
}
}
多条件组合逻辑
实际场景中用户往往需要组合多个过滤条件,比如同时满足多个规则,或者满足任意一个规则。我们可以通过扩展方法来组合多个谓词表达式,支持AND和OR两种逻辑:
public static class PredicateExtensions
{
// 组合多个谓词为AND逻辑
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
if (left == null) return right;
if (right == null) return left;
// 替换右表达式的参数,避免参数不一致
ParameterExpression parameter = left.Parameters[0];
Expression rightBody = new ParameterReplacer(right.Parameters[0], parameter).Visit(right.Body);
// 构建AND组合的Lambda表达式
BinaryExpression andExpression = Expression.AndAlso(left.Body, rightBody);
return Expression.Lambda<Func<T, bool>>(andExpression, parameter);
}
// 组合多个谓词为OR逻辑
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
if (left == null) return right;
if (right == null) return left;
ParameterExpression parameter = left.Parameters[0];
Expression rightBody = new ParameterReplacer(right.Parameters[0], parameter).Visit(right.Body);
BinaryExpression orExpression = Expression.OrElse(left.Body, rightBody);
return Expression.Lambda<Func<T, bool>>(orExpression, parameter);
}
// 参数替换器,用于将表达式中的参数替换为统一的参数
private class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _oldParameter;
private readonly ParameterExpression _newParameter;
public ParameterReplacer(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == _oldParameter)
{
return _newParameter;
}
return base.VisitParameter(node);
}
}
}
高性能优化方案
为了提升谓词过滤引擎的执行性能,我们可以从以下几个方面进行优化:
- 表达式缓存:对于相同的过滤条件,避免重复构建和编译表达式,将编译后的委托缓存起来,下次直接使用。可以使用字典存储条件哈希和对应的委托。
- 避免重复反射:提前缓存实体类型的属性信息,不需要每次解析动态变量时都通过反射获取属性,减少反射带来的性能开销。
- 简化表达式树:在构建表达式时,对于常量条件可以提前计算,避免生成冗余的表达式节点,减少编译后的委托执行步骤。
完整使用示例
以下是一个完整的使用示例,演示如何动态构建过滤条件并筛选用户集合:
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
// 模拟用户数据集
List<User> users = new List<User>
{
new User { Id = 1, Name = "张三", Age = 25, Role = "admin" },
new User { Id = 2, Name = "李四", Age = 30, Role = "user" },
new User { Id = 3, Name = "王五", Age = 22, Role = "user" },
new User { Id = 4, Name = "赵六", Age = 35, Role = "admin" }
};
// 动态构建过滤条件:年龄大于等于25 且 角色为admin
Expression<Func<User, bool>> predicate = null;
predicate = predicate.And(PredicateBuilder.BuildSinglePredicate<User>("Age", "gte", 25));
predicate = predicate.And(PredicateBuilder.BuildSinglePredicate<User>("Role", "eq", "admin"));
// 执行过滤
List<User> filteredUsers = users.AsQueryable().Where(predicate).ToList();
// 输出结果
Console.WriteLine("符合条件的用户:");
foreach (User user in filteredUsers)
{
Console.WriteLine($"Id:{user.Id}, Name:{user.Name}, Age:{user.Age}, Role:{user.Role}");
}
}
}
上述示例中,我们首先构建了两个条件,然后通过AND逻辑组合成最终的谓词,最后对集合进行筛选,执行结果会输出年龄大于等于25且角色为admin的用户,也就是Id为1和4的两条数据。
注意事项
在实际使用中需要注意动态变量的类型匹配问题,比如用户输入的目标值需要和实体属性的类型兼容,否则转换时会抛出异常。另外,如果过滤条件非常复杂,比如包含嵌套属性或者自定义函数调用,还需要扩展表达式构建的逻辑,支持更复杂的表达式节点类型。