EF Core 表达式树、结构、动态构造
EF Core 表达式树、结构、动态构造
EF Core 表达式树
1.表达式树(Expression Tree):树形数据结构表示代码以表示逻辑运算,以便可以在运行时访问逻辑运算的结构。
2.Expression <TDelegate>类型
3.从Lambda表达式来生成表达式树:
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);
//打印结果System.Func`2[Book,System.Boolean]
查看生成的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对象存储了运算逻辑,他把运算逻辑保存成抽象语法树(AST),可以在运行时动态获取运算逻辑。而普通委托则没有
如何查看表达式树的结构
1.VS中调试程序,然后用【快速监视】的方式查看变量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
https://github.com/zspitz/ExpressionTreeVisualizer
安装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#"));
小提示:using static xxx(可以直接调用该方法)
使用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
)
使用工厂模式构造表达式树:用此代码打印出来的与上述一样
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#"));
动态构建表达式树
using System.Linq.Expressions;
using System.Reflection;
using static System.Linq.Expressions.Expression;
var q1 = QueryBooks("Price", 65.0).ToList();
foreach (var item in q1)
{
Console.WriteLine("标题:"+item.Title+",发布时间:"+item.PubTime+",作者:"+item.AuthorName);
}
var q2 = QueryBooks("AuthorName", "first64").ToList();
foreach (var item in q2)
{
Console.WriteLine("标题:" + item.Title + ",发布时间:" + item.PubTime + ",作者:" + item.AuthorName);
}
var q3 = QueryBooks("Title", "零基础趣学C语言").ToList();
foreach (var item in q3)
{
Console.WriteLine("标题:" + item.Title + ",发布时间:" + item.PubTime + ",作者:" + item.AuthorName);
}
IEnumerable<Book> QueryBooks(string propName, object value)
{
Type type = typeof(Book);
PropertyInfo propInfo = type.GetProperty(propName);
Type propType = propInfo.PropertyType;
var b = Parameter(typeof(Book), "b");
Expression<Func<Book, bool>> expr;
if (propType.IsPrimitive)//如果是int、double等基本数据类型
{
expr = Lambda<Func<Book, bool>>(Equal(
MakeMemberAccess(b, typeof(Book).GetProperty(propName)),
Constant(value)), b);
}
else//如果是string等类型
{
expr = Lambda<Func<Book, bool>>(MakeBinary(ExpressionType.Equal,
MakeMemberAccess(b, typeof(Book).GetProperty(propName)),
Constant(value), false, propType.GetMethod("op_Equality")
), b);
}
TestDbContext ctx = new TestDbContext();
return ctx.Books.Where(expr).ToArray();
}
尽量避免使用动态构建表达式树
//尽量避免使用动态构建表达式树
var q2 = QueryBooks("net core 学习64",null,66,1).ToList();
foreach (var item in q2)
{
Console.WriteLine("标题:" + item.Title + ",发布时间:" + item.PubTime + ",作者:" + item.AuthorName);
}
Book[] QueryBooks(string title, double? lowerPrice, double? upperPrice, int orderByType)
{
using TestDbContext ctx = new TestDbContext();
IQueryable<Book> source = ctx.Books;
if (!string.IsNullOrEmpty(title))
{
source = source.Where(b => b.Title.Contains(title));
}
if (lowerPrice != null)
{
source = source.Where(b => b.Price >= lowerPrice);
}
if (upperPrice != null)
{
source = source.Where(b => b.Price <= upperPrice);
}
if (orderByType == 1)
{
source = source.OrderByDescending(b => b.Price);
}
else if (orderByType == 2)
{
source = source.OrderBy(b => b.Price);
}
return source.ToArray();
}
总结:
1.动态构建表达式树的代码很复杂,而且易读性差,维护起来不方便
2.一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译器确定要操作的类名,属性等,所以才需要编写动态构建表达式树的代码,否则为了提高代码的可读性和可维护性,要尽量避免构建表达式树。而是用IQueryable的延迟执行特性来动态构造。
第三方Nuget包 支持使用字符串拼接进行数据操作
System.Linq.Dynamic.Core 具体看官方文档
地址:https://github.com/zzzprojects/System.Linq.Dynamic.Core/blob/master/README.md
using System.Linq.Dynamic.Core;
using var ctx = new TestDbContext();
ctx.Books.Where($"Price > 0 and Price < 66").Select("new (Title,AuthorName)").ToDynamicArray();
生成的对应的SQL
SELECT t."Title", t."AuthorName"
FROM "T_Books" AS t
WHERE (t."Price" > 0.0) AND (t."Price" < 66.0)