-
表达式树——C#测试动态查询构建器
第一部分:C#基础入门
7.表达式树——动态查询构建器
实例介绍
在电商后台、数据分析系统中,动态查询是常见需求:用户可自定义筛选条件(如商品价格区间、类别、销量等),系统需实时生成符合条件的查询语句。硬编码无法覆盖所有可能的条件组合,而表达式树能在运行时动态构建LINQ查询表达式,完美解决这一问题。
本节通过商品动态筛选系vb.net教程C#教程python教程SQL教程access 2010教程
统的实现,带你掌握表达式树的核心用法——从单个条件构建到多条件组合(AND/OR),最终生成可直接用于EF Core或内存集合的查询逻辑。
需求分析
设计一个支持多条件组合的动态查询构建器,核心需求如下:
1.支持常见操作符:等于(==)、大于(>)、小于(<)、包含(Contains)、大于等于(>=)、小于等于(<=);
2.支持多条件组合:AND(&&)、OR(||);
3.支持多数据类型:int(销量)、decimal(价格)、string(类别/名称)、DateTime(创建时间);
4.生成LINQ表达式:输出Expression<Func<T, bool>>,可直接用于Where方法;
5.易用性:提供简洁API,开发者无需直接操作复杂的表达式树节点。
代码实现
-
核心模型类
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
// 商品模型(覆盖多数据类型)
public class Product
{
public int Id { get; set; }
public string Name { get; set; } // 字符串类型
public string Category { get; set; } // 字符串类型
public decimal Price { get; set; } // decimal类型
public int Sales { get; set; } // int类型
public DateTime CreateTime { get; set; } // DateTime类型
}
-
表达式树工具类(核心)
csharp
// 参数重绑定器:解决多表达式组合时参数不一致问题
internal class ParameterRebinder : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> _map;
public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
_map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_map.TryGetValue(node, out ParameterExpression replacement))
{
node = replacement;
}
return base.VisitParameter(node);
}
}
// 表达式树构建工具类
public static class ExpressionBuilder
{
// 构建"等于"条件(==)
public static Expression<Func<T, bool>> Equal<T>(string propertyName, object value)
{
return BuildBinaryExpression<T>(propertyName, value, Expression.Equal);
}
// 构建"大于"条件(>)
public static Expression<Func<T, bool>> GreaterThan<T>(string propertyName, object value)
{
return BuildBinaryExpression<T>(propertyName, value, Expression.GreaterThan);
}
// 构建"小于"条件(<)
public static Expression<Func<T, bool>> LessThan<T>(string propertyName, object value)
{
return BuildBinaryExpression<T>(propertyName, value, Expression.LessThan);
}
// 构建"包含"条件(Contains,仅适用于字符串)
public static Expression<Func<T, bool>> Contains<T>(string propertyName, string value)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
var method = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
var constant = Expression.Constant(value, typeof(string));
var call = Expression.Call(property, method, constant);
return Expression.Lambda<Func<T, bool>>(call, parameter);
}
// 组合两个条件(AND)
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
var map = new Dictionary<ParameterExpression, ParameterExpression>
{
{ right.Parameters[0], left.Parameters[0] }
};
var rightBody = ParameterRebinder.ReplaceParameters(map, right.Body);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body, rightBody), left.Parameters[0]);
}
// 组合两个条件(OR)
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
{
var map = new Dictionary<ParameterExpression, ParameterExpression>
{
{ right.Parameters[0], left.Parameters[0] }
};
var rightBody = ParameterRebinder.ReplaceParameters(map, right.Body);
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(left.Body, rightBody), left.Parameters[0]);
}
// 通用二元表达式构建(Equal/GreaterThan/LessThan共用)
private static Expression<Func<T, bool>> BuildBinaryExpression<T>(string propertyName, object value, Func<Expression, Expression, BinaryExpression> binaryOperator)
{
var parameter = Expression.Parameter(typeof(T), "x"); // 参数:x => ...
var property = Expression.Property(parameter, propertyName); // 属性访问:x.Price
var constant = Expression.Constant(Convert.ChangeType(value, property.Type), property.Type); //常量:1000(转换为属性类型)
var binary = binaryOperator(property, constant); // 二元操作:x.Price > 1000
return Expression.Lambda<Func<T, bool>>(binary, parameter); // 包装为Lambda:x => x.Price >1000
}
}
-
动态查询构建器(封装API)
csharp
// 动态查询构建器(泛型支持任意类型)
public class DynamicQueryBuilder<T>
{
private Expression<Func<T, bool>> _expression;
// 添加条件(默认AND组合)
public DynamicQueryBuilder<T> AddCondition(Expression<Func<T, bool>> condition, bool useAnd = true)
{
if (_expression == null)
{
_expression = condition;
}
else
{
_expression = useAnd ? _expression.AndAlso(condition) : _expression.OrElse(condition);
}
return this;
}
// 生成最终查询表达式
public Expression<Func<T, bool>> Build()
{
return _expression ?? (x => true); // 默认返回"始终为真"的表达式
}
// 直接编译为委托(用于内存集合查询)
public Func<T, bool> Compile()
{
return Build().Compile();
}
}
-
测试程序(商品筛选示例)
csharp
class Program
{
static void Main(string[] args)
{
// 模拟商品数据
var products = new List<Product>
{
new Product { Id=1, Name="笔记本电脑", Category="电子", Price=5999.99m, Sales=1200, CreateTime=new DateTime(2024,1,15) },
new Product { Id=2, Name="T恤", Category="服装", Price=99.99m, Sales=5000, CreateTime=new DateTime(2024,2,10) },
new Product { Id=3, Name="智能手机", Category="电子", Price=3999.99m, Sales=3000, CreateTime=new DateTime(2024,3,5) },
new Product { Id=4, Name="牛奶", Category="食品", Price=59.99m, Sales=10000, CreateTime=new DateTime(2024,4,20) },
new Product { Id=5, Name="平板电脑", Category="电子", Price=2999.99m, Sales=800, CreateTime=new DateTime(2024,5,1) }
};
// 动态构建查询条件:类别=电子 AND 价格>2000 AND 销量>1000
var queryBuilder = new DynamicQueryBuilder<Product>()
.AddCondition(ExpressionBuilder.Equal<Product>("Category", "电子"))
.AddCondition(ExpressionBuilder.GreaterThan<Product>("Price", 2000m))
.AddCondition(ExpressionBuilder.GreaterThan<Product>("Sales", 1000));
// 生成查询并执行
var query = queryBuilder.Build();
var result = products.Where(query).ToList();
// 输出结果
Console.WriteLine("=== 筛选结果(电子类+价格>2000+销量>1000) ===");
foreach (var p in result)
{
Console.WriteLine($"ID:{p.Id} | 名称:{p.Name} | 价格:{p.Price:C} | 销量:{p.Sales}");
}
// 扩展:添加"名称包含手机"的OR条件
queryBuilder.AddCondition(ExpressionBuilder.Contains<Product>("Name", "手机"), useAnd: false);
var extendedResult = products.Where(queryBuilder.Build()).ToList();
Console.WriteLine("
=== 扩展结果(原条件 OR 名称含手机) ===");
foreach (var p in extendedResult)
{
Console.WriteLine($"ID:{p.Id} | 名称:{p.Name} | 价格:{p.Price:C} | 销量:{p.Sales}");
}
}
}
逐行讲解
-
ParameterRebinder类
作用:解决多表达式组合时参数不一致问题(如两个表达式的参数分别是x和y,需统一为x);
核心方法:VisitParameter——重写ExpressionVisitor的方法,替换参数为目标参数; -
ExpressionBuilder工具类
Equal/GreaterThan/LessThan:通过BuildBinaryExpression通用方法构建二元操作表达式(如x.Price>2000);
Contains:构建字符串Contains方法调用表达式(如x.Name.Contains("手机")),需反射获取string的Contains方法;
AndAlso/OrElse:组合两个条件表达式,使用ParameterRebinder统一参数; -
DynamicQueryBuilder类
AddCondition:链式添加条件,支持AND/OR组合;
Build:生成最终查询表达式,默认返回"始终为真"的表达式(避免空条件);
Compile:将表达式编译为委托,适用于内存集合查询; -
测试程序
模拟数据:创建5个商品,覆盖不同类别、价格、销量;
条件构建:组合"电子类+价格>2000+销量>1000"三个条件;
执行查询:使用Where方法执行动态生成的表达式,输出结果;
扩展条件:添加OR条件"名称包含手机",验证组合逻辑正确性;
基础知识拓展 -
表达式树核心概念
表达式树结构:由节点组成,常见节点类型:
ParameterExpression:参数节点(如x);
MemberExpression:成员访问节点(如x.Price);
ConstantExpression:常量节点(如2000);
BinaryExpression:二元操作节点(如x.Price>2000);
MethodCallExpression:方法调用节点(如x.Name.Contains("手机"));
不可变性:表达式树节点默认不可修改,修改需重新构建;
编译与执行:通过Compile()方法将表达式树转换为委托,执行委托获取结果; - 表达式树与委托的区别
| 特性 | 表达式树(Expression<Func<T,bool>>) | 委托(Func<T,bool>) |
|---|---|---|
| 存储形式 | 抽象语法树(AST) | 编译后的IL代码 |
| 运行时操作 | 可解析、修改、转换为SQL | 仅可执行 |
| EF Core支持 | 支持(转换为SQL) | 不支持(内存过滤) |
| 性能 | 首次编译耗时,后续复用高效 | 直接执行高效 |
-
表达式树在EF Core中的应用
EF Core会解析表达式树,生成对应的SQL语句(如WHERE Category='电子' AND Price>2000);
若使用委托(Func<T,bool>),EF Core会先加载所有数据到内存,再执行委托过滤,性能极低; -
常见应用场景
动态查询:如电商筛选、报表自定义条件;
ORM框架:如EF Core、Dapper(部分支持);
规则引擎:如动态验证规则、工作流条件;
序列化与反序列化:如将表达式树转换为JSON,跨进程传输;
总结
表达式树是C#中动态生成代码的强大工具,核心价值在于运行时构建逻辑。通过本节学习,你掌握了:
表达式树的基本构建方法(二元操作、方法调用);
多条件组合(AND/OR)的实现;
动态查询构建器的封装与使用;
表达式树在EF Core中的应用要点;
掌握表达式树,能让你写出更灵活、更高效的动态逻辑,尤其在需要自定义查询或规则的场景中,表达式树是不可或缺的技术。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49429.html
最新更新
动态类型——C#编写COM组件交互
表达式树——C#测试动态查询构建器
LINQ高级查询——C#进行复杂数据分组与聚
异步IO操作——C#编写关于大文件多线程下
异步编程基础——C#开发Task异步方法设计
事件驱动编程——C#开发实时消息通知系
委托与Lambda——C#简化事件处理逻辑
泛型集合自定义——C#关于类型安全的队
Excel操作——用C#开发学生成绩表生成(
CSV文件处理——C#编写销售数据导入导出
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比










