《ASP.NET Core技术内幕与项目实战》精简集-EFCore2.6:表达式树Expression和Func
本节内容,涉及5.3(P142-P153)。主要NuGet包:
- ExpressionTreeToString(用于以string格式,输出表达式树)
- System.Linq.Dynamic.Core(通过字符串形式,非常简单的构建表达式树,可以不用自己构建表达式树)
一、Expression(表达式树)与Func(Lambda表达式)的区别
//代码1 var articles = ctx.Articles.Where(a => a.Id > 0); foreach (var item in articles) { Console.WriteLine(item.Title); } //代码2 Func<Article, bool> f1 = a => a.Id > 0; var articles = ctx.Articles.Where(f1); foreach (var item in articles) { Console.WriteLine(item.Title); } //代码3 Expression<Func<Article, bool>> e1 = a => a.Id > 0; var articles = ctx.Articles.Where(e1); foreach (var item in articles) { Console.WriteLine(item.Title); }
代码解读:
①代码1和代码2的执行的结果一样,但过程不一样
②代码1:在数据库上执行a.Id>0的查询,然后返回数据(服务端评估),变量articles的类型为IQueryable
③代码2:先从数据库返回所有数据,然后在服务器内存中执行LINQ(a.Id>0)的查询(客户端评估),变量articles的类型为IEnumerable
④代码1:Where方法中,虽然形式上是一个Lambda表达式,但编译器自动将Lambda表达式,转换为Expression,实质上参数类型为Expression<Func<Article,bool>>
⑤代码2:实质上可以看成两步:第一步执行ctx.Articles().ToList(),第二步执行LINQ中的Where方法。
⑥代码3等效于代码1
二、为什么需要Expression
1、如一所示,如果我们要使用IQueryable,要么在Where方法参数中写入Lambda表达式,要么申明一个Expression类型的变量,Where方法中传入变量。实质上,以上两种方式,都是编译器帮助我们生成表达式树。
2、无论哪种方式,都是在开发时硬编码,然后借助编译器帮助我们生成表达式树。如果我们想在运行时,动态生成Where的查询条件,没有了编译器的帮助,我们就需要自己构建一个表达式树了。
3、安装ExpressionTreeToString库,我们可以将编译器帮我们生成的表达式树输出,表达式树的格式是非常复杂的,涉及到编译器和算法层面的知识。自己构建表达式树,也是一个非常复杂的事情。如下所示:
//表达式树在编译器中的样子,很简洁,就是一个Lambda Expression<Func<Article, bool>> e1 = a => a.Id > 0; //借助ExpressionTreeToString类库提供的扩展方法ToString,将表达式树输出到控制台 Console.WriteLine(e1.ToString("Object notation","C#")); //表达式树实际的样式,非常繁复 var a = new ParameterExpression { Type = typeof(Article), IsByRef = false, Name = "a" }; new Expression<Func<Article, bool>> { NodeType = ExpressionType.Lambda, Type = typeof(Func<Article, bool>), Parameters = new ReadOnlyCollection<ParameterExpression> { a }, Body = new BinaryExpression { NodeType = ExpressionType.GreaterThan, Type = typeof(bool), Left = new MemberExpression { Type = typeof(long), Expression = a, Member = typeof(Article).GetProperty("Id") }, Right = new ConstantExpression { Type = typeof(long), Value = 0 } }, ReturnType = typeof(bool) }
三、实际项目中,需要自己构建表达式树吗?
1、除非是自己要开发一个框架,否则一般的业务项目,都极少会需要自己构建表达式树
2、就算要构建运行时的动态查询,首先想到的,也是利用IQueryable的延迟执行和复用的特性来完成
3、如果无法使用IQueryable,还可以有两种方式进行动态查询。一是自己构建表达式树,二是使用DynamicLinq,直接输入查询字符串。
4、对表达式树的深入使用,可以详见书本146-151
①借助ExpressionTreeToString库,先输出表达式树,然后进行少量改写即可,如上所示的表达式树,我们直接使用如下:
//ToString方法的参数,改成Factory methods,这种方式输出的表达式树可以直接使用 Expression<Func<Article, bool>> e1 = a => a.Id > 0; Console.WriteLine(e1.ToString("Factory methods","C#")); //输出的表达式树如下所示: // using static System.Linq.Expressions.Expression var a = Parameter( typeof(Article), "a" ); Lambda( GreaterThan( MakeMemberAccess(a, typeof(Article).GetProperty("Id") ), Constant(0) ), a ) //对输出的表达式树进行改造,完成动态创建表达式树的目的 //可以看出几乎一模一样 var a = Parameter( typeof(Article), "a" ); var expr1 = Lambda<Func<Article,bool>>( GreaterThan( MakeMemberAccess(a, typeof(Article).GetProperty("Id") ), Constant(0) ), a ); ctx.Articles.Where(expr1).ToList();
②借助System.Linq.Dynamic.Core类库提供的扩展方法,我们可以输入FormattableString格式的字符串作为查询参数,从而更加简单的构建运行时的动态查询。
//动态构建查询条件 //WhereInterpolated,由System.Linq.Dynamic.Core提供的扩展方法 //除了WhereInterpolated之外,还有一系列以Interpolated结尾的扩展方法 var id = 1; var word = "文章"; var articles = ctx.Articles.WhereInterpolated($"Id>{id} and Title.Contains({word})"); foreach (var item in articles) { Console.WriteLine(item.Title); }
特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍