expression树不是lambda等价替换,而是可编译、遍历、转换的代码结构;必须用expression的场景是需运行时查看或修改逻辑,如orm查询、动态筛选、规则引擎、aop拦截。

Expression 表达式树不是 Lambda 的等价替换,它本质是可编译、可遍历、可转换的代码结构;直接用 Func<t></t> 能解决的问题,别硬套 Expression<func>></func> —— 否则你会在运行时报 NotSupportedException 或查不到字段。
什么时候必须用 Expression 而不是 Func?
核心判断依据:是否需要在运行时「看到」或「修改」代码逻辑本身。常见于 ORM 查询(如 EF Core)、动态筛选、规则引擎、AOP 参数拦截等场景。
- EF Core 的
Where()接收Expression<func bool>></func>,因为要翻译成 SQL;传Func<t bool></t>会触发客户端求值,性能崩盘甚至报错 - 你需要根据用户输入拼接条件(比如搜索框+下拉筛选),就得构建
Expression树,而不是提前编译成Func - 想对某个属性访问做统一日志或校验(如所有
x.Name访问前加检查),必须从表达式中提取MemberExpression,Func里你只能拿到结果值
ParameterExpression 绑定失败:变量名不匹配是最常见坑
手动构建表达式树时,ParameterExpression 是入口,但它的 Name 属性只是调试用,真正绑定靠的是引用相等 —— 不是字符串名相同就行。
- 错误写法:
var p1 = Expression.Parameter(typeof(User), "u"); var p2 = Expression.Parameter(typeof(User), "u");,然后用p2去构建 body,再用p1创建LambdaExpression→ 运行时报InvalidOperationException: variable 'u' of type 'User' referenced from scope '' but it is not defined - 正确做法:所有子表达式(
MemberAccess、BinaryExpression等)中出现的参数,必须是同一个ParameterExpression实例 - EF Core 中如果你用
Expression.Invoke()拼接多个子表达式,也得确保它们共享同一参数实例,否则翻译 SQL 时会丢参数
Expression.Compile() 的开销和缓存策略
Compile() 是把表达式树转成可执行委托的过程,耗时明显 —— 在循环里反复调用等于自废武功。
- 单次使用(如配置初始化阶段):直接
expr.Compile()即可 - 高频调用(如每请求一次的过滤器):必须缓存编译结果,推荐用
ConcurrentDictionary<string delegate></string>或Lazy<func>></func>包裹 - 注意泛型类型擦除:
Expression<func>></func>和Expression<func>></func>编译后是不同委托类型,不能混用缓存 key - Debug 模式下
Compile()会额外生成调试信息,比 Release 慢 3–5 倍,压测时务必用 Release 构建
EF Core 中 Expression 无法翻译的典型错误
不是所有 C# 语法都能被 EF Core 转成 SQL,一旦遇到不支持的节点,就会 fallback 到客户端求值(ClientEval)或直接抛 InvalidOperationException。
-
ToString()、DateTime.Now、new Guid():多数版本不支持,改用EF.Functions...或数据库函数(如EF.Functions.DateDiffDay()) - 自定义方法(哪怕标记
[DbFunction]):必须在OnModelCreating中显式映射,且方法体不能含非可翻译逻辑 - 嵌套
Expression.Invoke():EF Core 6+ 才开始有限支持,旧版直接报错;更稳妥的方式是用ExpressionVisitor内联展开 - 检查方式:启用 EF 日志(
EnableSensitiveDataLogging),看生成的 SQL 是否包含AS [t]或WHERE 1=0类提示,那基本就是客户端求值了
最麻烦的其实是调试:表达式树没有源码行号,ToString() 输出又难读。真要深挖,得靠 ExpressionVisitor 逐层 Visit 打印,或者用 ExpressionTreeToString 这类开源小工具辅助 —— 别指望 IDE 调试窗口能看清嵌套三层以上的 BinaryExpression。










