expression.compile() 性能代价高,高频调用时比硬编码慢10倍以上;需缓存委托、避免循环编译;ef core中应避免自定义方法导致客户端求值;parameterexpression须复用同一实例;lambda必须显式指定泛型类型。

Expression.Compile() 性能代价比你想的高
直接调用 Expression.Compile() 生成委托,每次执行都走 JIT 编译,反复调用时会成为性能瓶颈。尤其在高频查询(比如 Web API 每次请求构造条件)中,它可能比硬编码 Where(x => x.Status == "A") 慢 10 倍以上。
- 只在「条件结构稳定、编译后可复用」时才调用一次
Compile(),缓存返回的Func<t bool></t>或Expression<func bool>></func> - 避免在循环里写
expr.Compile()()——这等于每轮都重新编译+执行 - 若只是拼接 SQL(如 EF Core),根本不需要
Compile();EF 会把Expression直接翻译成 SQL,此时保留表达式树本身更安全高效
ParameterExpression 重名会导致 Expression.Invoke 报错
用 Expression.Invoke() 组合多个子表达式时,如果两个 ParameterExpression 名字一样(哪怕类型相同),.NET 会抛出 ArgumentException: The parameter 'x' was not bound in the expression。这不是变量冲突,而是表达式树内部绑定机制要求参数对象必须是同一实例。
- 所有参与组合的子表达式,必须共用同一个
ParameterExpression实例,而不是新建同名的 - 别这么写:
var p1 = Expression.Parameter(typeof(User), "u"); var p2 = Expression.Parameter(typeof(User), "u");—— 即使名字和类型一致,p1 != p2 - 正确做法:先定义
var param = Expression.Parameter(typeof(User), "u"),后续所有PropertyAccess、Equal都基于它构建
EF Core 中 Expression 被“吃掉”导致查询不生效
把自定义方法塞进表达式树(比如 Expression.Call(typeof(MyUtil).GetMethod("IsAdult"), ...)),EF Core 默认不认识这个方法,会直接忽略整段逻辑,甚至静默退化为客户端求值(ToList() 后再过滤),查出全部数据再内存过滤。
- EF Core 只翻译它内置支持的函数(如
string.Contains、DateTime.Now)和已注册的自定义函数(通过ModelBuilder.HasDbFunction()) - 检查日志输出是否含
Client evaluation关键字;有就说明表达式没被服务端翻译 - 简单条件尽量用基础表达式(
Equal、AndAlso、MemberAccess)组合,绕开自定义方法调用
Expression.Lambda() 的泛型参数不能靠推断
写 Expression.Lambda(body, param) 看似简单,但漏掉泛型参数会导致运行时报 InvalidOperationException: Cannot convert lambda expression to intended delegate type,尤其在传给 IQueryable.Where() 时。
- 必须显式指定委托类型,例如:
Expression.Lambda<func bool>>(body, param)</func>,不能只写Expression.Lambda(body, param) - 泛型参数要和目标 API 匹配:传给
Where()就用Func<t bool></t>,传给OrderBy()就用Func<t tkey></t> - 若不确定类型,用
typeof(Func).MakeGenericType(...)动态构造,但多数场景下直接写死更稳
最常被跳过的其实是参数复用和泛型声明这两步——看起来只是多敲几个字符,但错一个就整个表达式树跑不起来,而且错误信息完全不提示你缺了什么。











