跟着AI学习C# Day23

📅 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#),你将学会如何识别常见设计模式、理解其应用场景,并掌握单例、工厂、策略等几种最常用的模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值