什么是表达式树
- 表达式树(Expression Tree):树形数据结构表示代码以表示逻辑运算,以便可以在运行时访问逻辑运算的结构。
- Expression <TDelegate>类型
- 从Lambda表达式来生成表达式树:
Expression<Func<Book,bool>> expression = b=>.Price > 5;
Expression和Func的区别
Expression<Func<Book,bool>> expression = b=>.Price > 5;
Console.WriteLine(expression);//e => (e.Price > 5) 打印结果
//把Expression<Func<Book,bool>>换成Func<Book,bool>如下
Func<Book,bool> func = b=>.Price > 5;
Console.WriteLine(func);
通过运行发现,两者执行效果一样
查看生成的SQL语句
-- Expression<Func<Book,bool>> expression = b=>.Price > 5;
SELECT t."Id", t."AuthorName", t."Price", t."PubTime", t."Title"
FROM "T_Books" AS t
WHERE t."Price" > 5.0
--Func<Book,bool> func = b=>.Price > 5;
SELECT t."Id", t."AuthorName", t."Price", t."PubTime", t."Title"
FROM "T_Books" AS t
结论
Expression表达树是编译器可运行时从内存中分析出表达式的结构,并把它翻译成SQL语句,Expression表达树在数据库就已经筛选了数据。
Func是把所有数据都查询出来,在内存中调用委托,来进行对应的数据查询,如果数据量大,这种处理是不合适的。
如何查看表达式树的结构
Visual Studio中调试程序,然后用【快速监视】的方式查看变量expression的值,展开Raw View.
Expression<Func<Book, bool>> e = b => b.Price > 5 || b.AuthorName.Contains("杨");
2.整个表达式树是一个“或”(OrElse)类型的节点,左节点(Left)是b.Price>5表达式,右节点(Right)是b.Authorname.Contains(“杨”)表达式。而b.Price>5这个表达式又是一个“大于”(GreaterThan)类型的节点,左节点(Left)是b.Price,右节点(Right)是5.
3.AST:抽象语法树
1、通过zspitz开发的ExpressionTreeVisualizer
GitHub - zspitz/ExpressionTreeVisualizer: Debugging visualizer for expression trees
安装VS插件(由于这个插件对版本要求很严格,所以不推荐使用)
2、通过代码查看表达式树
推荐使用代码查看结构,简单方便
安装ExpressionTreeToString Nuget包
//使用方式如下
Expression<Func<Book, bool>> e = b => b.AuthorName.Contains("杨") || b.Price > 30;
Console.WriteLine(e.ToString("Object notation", "C#"));
生成表达式树的结构代码
var b = new ParameterExpression {//参数
Type = typeof(Book),//Book类型
IsByRef = false,
Name = "b" //参数名字为b
};
new Expression<Func<Book, bool>> {
NodeType = ExpressionType.Lambda,//根节点是一个Lambda
Type = typeof(Func<Book, bool>),
Parameters = new ReadOnlyCollection<ParameterExpression> {
b
},//整个Lambda表达式有一个参数 b
Body = new BinaryExpression {//Lambda的体(Body)是一个二元表达式
NodeType = ExpressionType.GreaterThan,//二元表达式的节点类型是 大于 >
Type = typeof(bool),
Left = new MemberExpression {//左节点是 访问成员表达式
Type = typeof(double),
Expression = b,
Member = typeof(Book).GetProperty("Price")
},
Right = new ConstantExpression {//右节点是 常量表达式
Type = typeof(double),//double类型
Value = 5 //值为5 我们写代码的时候 double v= 5;编译器帮我们转化了
}
},
ReturnType = typeof(bool)
}
通过代码动态构造表达式树
注:比较麻烦,而且容易报错
思想:先创建树叶,再通过一片片树叶组成大树
//动态创建和Expression<Func<Book, bool>> e = b =>b.Price > 5一样的代码
ParameterExpression paramB = Expression.Parameter(typeof(Book), "b");//参数类型与名字
MemberExpression exprLeft = Expression.MakeMemberAccess(paramB, typeof(Book).GetProperty("Price"));//成员访问表达式左节点
//右节点是 常量表达式
ConstantExpression exprRight = Expression.Constant(5.0, typeof(double));
//二元表达式的节点类型是 大于 >
BinaryExpression exprBody = Expression.MakeBinary(ExpressionType.GreaterThan, exprLeft, exprRight);
Expression<Func<Book, bool>> expr1 = Expression.Lambda<Func<Book, bool>>(exprBody, paramB);
ctx.Books.Where(expr1).ToList();
Console.WriteLine(expr1.ToString("Object notation", "C#"));
可以用ExpressionTreeToString的ToString("Factory methods", "C#")输出类似于工厂方法生成这个表达式树的代码。
使用Factory methods 生成的表达式树是以工厂模式生成的 比上述原生要简单许多
Expression<Func<Book, bool>> e = b => b.Price > 5;
Console.WriteLine(e.ToString("Factory methods", "C#"));
工厂模式生成的表达式树
var b = Parameter(
typeof(Book),
"b"
);
Lambda(
GreaterThan(
MakeMemberAccess(b,
typeof(Book).GetProperty("Price")
),
Constant(5)
),
b
)
注意:使用Using static引用调用静态方法不需要再加类名。
使用工厂模式构造表达式树:用此代码打印出来的与上述一样
var b = Parameter(typeof(Book), "b");
var expr1 = Lambda<Func<Book, bool>>(GreaterThan(MakeMemberAccess(b, typeof(Book).GetProperty("Price")),
Constant(5.0)), b);
using TestDbContext ctx = new TestDbContext();
ctx.Books.Where(expr1).ToList();
Console.WriteLine(expr1.ToString("Factory methods", "C#"));
尽量避免使用动态构建表达式树
- 动态构建表达式树的代码很复杂,而且易读性差,维护起来不方便
- 一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译器确定要操作的类名,属性等,所以才需要编写动态构建表达式树的代码,否则为了提高代码的可读性和可维护性,要尽量避免构建表达式树。而是用IQueryable的延迟执行特性来动态构造。