📅 Day 23:表达式树(Expression Trees)与动态查询构建
✅ 学习目标:
- 理解什么是 表达式树(Expression Tree);
- 掌握如何手动构建和编译表达式树;
- 理解表达式树在 LINQ、EF Core 中的作用;
- 学会使用表达式树实现动态条件查询;
- 能够构建可复用的查询逻辑(如动态过滤器);
- 编写一个基于表达式树的通用搜索功能示例。
🧠 一、什么是表达式树?
表达式树(Expression Tree) 是一种数据结构,它以树状形式表示代码逻辑。不同于普通的委托(如 Func<T, bool>
),表达式树可以在运行时被查看、修改甚至转换为 SQL 查询语句。
主要用途:
场景 | 示例 |
---|---|
LINQ to SQL / EF Core | 将 C# 表达式转换为 SQL 查询 |
动态查询构建 | 根据用户输入构造筛选条件 |
自定义规则引擎 | 构建可配置的业务逻辑 |
性能优化 | 避免反射调用,构建高效委托 |
🔁 二、表达式树 vs 委托(Func<>
)
特性 | Func<> 委托 | 表达式树 Expression<Func<>> |
---|---|---|
可执行 | ✅ | ❌(需要编译后才能执行) |
可序列化 | ❌ | ✅(可用于远程传输或持久化) |
可分析 | ❌ | ✅(可解析其结构) |
是否可转换为 SQL | ❌ | ✅(用于 EF Core 查询) |
🧩 三、基本结构与构建方式
示例:创建一个 x => x.Age > 18
的表达式树
// 参数:x
ParameterExpression param = Expression.Parameter(typeof(Person), "x");
// 属性访问:x.Age
MemberExpression ageProperty = Expression.Property(param, "Age");
// 常量:18
ConstantExpression constant = Expression.Constant(18);
// 比较表达式:x.Age > 18
BinaryExpression condition = Expression.GreaterThan(ageProperty, constant);
// 整体表达式:x => x.Age > 18
Expression<Func<Person, bool>> lambda = Expression.Lambda<Func<Person, bool>>(condition, param);
你可以将这个表达式用于 LINQ 查询:
var adults = people.Where(lambda.Compile());
⚙️ 四、表达式树常用节点类型
节点类型 | 描述 |
---|---|
ParameterExpression | 表示参数(如 x ) |
MemberExpression | 访问属性或字段(如 x.Name ) |
ConstantExpression | 表示常量值(如 "Hello" ) |
BinaryExpression | 表示运算符(如 > 、== ) |
MethodCallExpression | 表示方法调用(如 string.Contains() ) |
LambdaExpression | 包装整个 Lambda 表达式 |
🧱 五、动态构建复杂查询条件
你可以根据用户输入动态拼接多个条件:
public static Expression<Func<T, bool>> BuildFilter<T>(Dictionary<string, object> filters)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression body = Expression.Constant(true); // 初始条件为 true
foreach (var filter in filters)
{
MemberExpression property = Expression.Property(param, filter.Key);
ConstantExpression value = Expression.Constant(filter.Value);
BinaryExpression condition = Expression.Equal(property, value);
body = Expression.AndAlso(body, condition);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
使用示例:
var filters = new Dictionary<string, object>
{
{ "Name", "张三" },
{ "Age", 25 }
};
Expression<Func<Person, bool>> expr = BuildFilter<Person>(filters);
List<Person> result = people.Where(expr.Compile()).ToList();
💡 六、结合 Entity Framework 使用表达式树
在 EF Core 中,表达式树会被自动转换为 SQL 查询,因此你不能使用 .Compile()
。
✅ 正确方式:
var query = context.People.Where(expr); // 不编译,直接传入 EF
❌ 错误方式:
var query = context.People.Where(expr.Compile()); // 报错,EF 无法解析
🧪 七、构建支持 Contains
的动态查询(模糊匹配)
public static Expression<Func<T, bool>> BuildSearchExpression<T>(
string propertyName, string keyword)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression property = Expression.Property(param, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
ConstantExpression value = Expression.Constant(keyword);
MethodCallExpression containsCall = Expression.Call(property, method, value);
return Expression.Lambda<Func<T, bool>>(containsCall, param);
}
使用示例:
Expression<Func<Person, bool>> expr = BuildSearchExpression<Person>("Name", "李");
var results = people.Where(expr.Compile()).ToList();
💪 实战练习:通用动态查询过滤器
功能要求:
- 支持多个字段的等值查询;
- 支持模糊搜索(包含关键字);
- 支持排序字段和方向;
- 返回符合条件的结果列表。
示例代码框架:
class DynamicQuery<T>
{
private List<Expression<Func<T, bool>>> _filters = new();
public DynamicQuery<T> EqualsTo(string propertyName, object value)
{
var expr = BuildEqualsFilter(propertyName, value);
_filters.Add(expr);
return this;
}
public DynamicQuery<T> Contains(string propertyName, string keyword)
{
var expr = BuildSearchExpression<T>(propertyName, keyword);
_filters.Add(expr);
return this;
}
public Func<IQueryable<T>, IOrderedQueryable<T>> OrderBy(string propertyName, bool descending = false)
{
return source =>
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, propertyName);
var lambda = Expression.Lambda(property, param);
string methodName = descending ? "OrderByDescending" : "OrderBy";
var result = (IOrderedQueryable<T>)typeof(Queryable)
.GetMethods()
.Single(m => m.Name == methodName && m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), property.Type)
.Invoke(null, new object[] { source, lambda });
return result;
};
}
public List<T> Execute(IEnumerable<T> data)
{
var predicate = _filters.Aggregate((a, b) => a.And(b));
return data.AsQueryable().Where(predicate).ToList();
}
// 辅助方法:BuildEqualsFilter、BuildSearchExpression 等
}
📝 小结
今天你学会了:
- 什么是表达式树及其与普通委托的区别;
- 如何手动构建表达式树并将其编译为可执行的委托;
- 掌握了如何使用表达式树实现动态查询条件;
- 理解了表达式树在 LINQ、EF Core 查询中的作用;
- 编写了多个实用的表达式树构建函数;
- 完成了一个通用的动态查询过滤器实战项目。
表达式树是 C# 中非常强大的工具之一,尤其适用于构建灵活的查询系统、ORM 映射、自定义规则引擎等高级开发场景。
🧩 下一步学习方向(Day 24)
明天我们将进入一个新的主题 —— C# 中的设计模式入门(Design Patterns in C#),你将学会如何识别常见设计模式、理解其应用场景,并掌握单例、工厂、策略等几种最常用的模式。