《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倍

 

posted @ 2022-10-29 15:31  functionMC  阅读(391)  评论(0编辑  收藏  举报