C# 表达式树讲解(一)


一、前言

一直想写一篇Dpper的定制化扩展的文章,但是里面会设计到对Lambda表达式的解析,而解析Lambda表达式,就必须要知道表达式树的相关知识点。我希望能通过对各个模块的知识点或者运用能够多一点的讲解,能够帮助到园友了解得更多。虽然讲解得不全面,如果能成为打开这块的一把钥匙,也是蜗牛比较欣慰的。

表达式系列目录

C# 表达式树讲解(一)

​C# 表达式树遍历(二)​

​C# 表达式树分页扩展(三)​

​C# 表达式树Lambda扩展(四)​

二、表达树理解

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,它将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。在.Net 里面的Linq to SQL就是对表达式树的解析。

这里先讲解下表达式和表达式树,表达式相信大家都知道,比如x+5或者5,都可以算是表达式,而表达式树里面的树指的二叉树,也就是表达式的集合,C#中的Expression类就是表达式类。对于一棵表达式树,其叶子节点都是参数或者常数,非叶子节点都是运算符或者控制符。

2.1、表达式的创建

Lambda表达式方法:


Expression<Func<int, int,bool>> fun = (x, y) => x < y


这种方法创建出的表达式根节点类型为ExpressionType.Lambda,Type类型为返回值类型typeof(bool)

组装法(通过 API 创建表达式树):

C# 表达式树讲解_ide

ParameterExpression numParam = Expression.Parameter(typeof(int), "num"); ConstantExpression five = Expression.Constant(5, typeof(int)); BinaryExpression numLessThanFive = Expression.LessThan(numParam, five); Expression<Func<int, bool>> lambda1 =     Expression.Lambda<Func<int, bool>>(         numLessThanFive,         new ParameterExpression[] { numParam });

C# 表达式树讲解_c#_02

我们先创建了两个参数表达式num和5,然后用LessThan组装在一起,最终的表达式为“num<5”,expr的节点类型为LessThan,Type类型为typeof(bool)

我们先看看表达式树里面的构造

首先Expression<TDelegate>的功能是将强类型Lambda表达式表示为表达式树形式的数据结构,他的父类是LambdaExpression,比较他们代码可知,Lambda表达式的主体,名称和参数全部保存在LambdaExpression里面。

Expression<TDelegate>与LambdaExpression代码截图:

C# 表达式树讲解_c#_03

C# 表达式树讲解_lambda表达式_04

LambdaExpression里面的Body就是我们的表达式。

C#表达式给我们提供了丰富的表达式类,进入到LambdaExpression类里面

C# 表达式树讲解_表达式树_05

方法返回类型以“Expression”结尾的,基本上都是一个表达式类。

每个表达式代表的定义和创建方法,可以参照微软官方文档​​https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.binaryexpression?view=netframework-4.8​

下面是平常使用最多的表达式

ConstantExpression:常量表达式

ParameterExpression:参数表达式

UnaryExpression:一元运算符表达式

BinaryExpression:二元运算符表达式

TypeBinaryExpression:is运算符表达式

ConditionalExpression:条件表达式

MemberExpression:访问字段或属性表达式

MethodCallExpression:调用成员函数表达式

Expression<TDelegate>:委托表达式

2.2、表达式的解析

表达式树解析

通过LambdaExpression类我们可以知道,表达式树包含:参数[Parameters],表达式树类型[NodeType],表达式[Body],返回类型[ReturnType],Lambda表达式的委托[Compile]以及Lambda表达式名称[name],如图所示:

C# 表达式树讲解_lambda表达式_06

表达式解析:

所有的表达式都包含:左节点【Left】,右节点【Right】,类型【NodeType】,不同的表达式还会有其他属性,这里的左右节点依旧是表达式。

下图是BinaryExpression表达式截图

C# 表达式树讲解_分页_07

表达式树和表达式里面的类型NodeType是一个枚举,一共有85个类型,有兴趣的朋友可以去了解下。

常用的类型如下:

ExpressionType.And:C#中类似于&

ExpressionType.AndAlso:C#中类似于&&

ExpressionType.Or:C#中类似于|

ExpressionType.OrElse:C#中类似于||

ExpressionType.Equal:C#中类似于==

ExpressionType.NotEqual:C#中类似于!=

ExpressionType.GreaterThan:C#中类似于>

ExpressionType.GreaterThanOrEqual:C#中类似于>=

ExpressionType.LessThan:C#中类似于<

ExpressionType.LessThanOrEqual:C#中类似于<=

ExpressionType.Add:C#中类似于+

ExpressionType.AddChecked:C#中类似于+

ExpressionType.Subtract:C#中类似于-

ExpressionType.SubtractChecked:C#中类似于-

ExpressionType.Divide:C#中类似于/

ExpressionType.Multiply:C#中类似于*

ExpressionType.MultiplyChecked:C#中类似于*

2.3、编译表达式树

在表达式创建那,我们组合创建了一个Lambda表达式,那么应该怎么使用它呢?在“表达式的解析”里面,LambdaExpression类和Expression<TDelegate>类都有一个Compile的方法,学名是Lambda表达式的委托,其实就是Lambda表达式编译函数的委托,所以我们只需要调用他,得到的结果就是一个函数方法。

代码修改如下:


C# 表达式树讲解_分页_08

ParameterExpression numParam = Expression.Parameter(typeof(int), "num"); ConstantExpression five = Expression.Constant(5, typeof(int)); BinaryExpression numLessThanFive = Expression.LessThan(numParam, five); Expression<Func<int, bool>> lambda1 =     Expression.Lambda<Func<int, bool>>(         numLessThanFive,         new ParameterExpression[] { numParam });  Console.WriteLine($"Lambda的内容:{lambda1.ToString()}");  //表达式的编译 var func = lambda1.Compile(); Console.WriteLine($"Lambda的运行结果:{func(6)}");

C# 表达式树讲解_分页_09

运行结果

C# 表达式树讲解_lambda表达式_10

三、总结

这里我们对表达式做了基本的讲解,相信大家对Lambda表达式有了初步的了解,下面我们将继续讲解对一个表达式树的遍历。



出处:javascript:void(0)

=======================================================================================

C# 表达式树遍历(二)


一、前言

​上一篇​​我们对表达式树有了初步的认识,这里我们将对表达式树进行遍历,只有弄清楚了他的运行原理,我们才可以对他进行定制化修改。

表达式系列目录

​C# 表达式树讲解(一)​

C# 表达式树遍历(二)

​ C# 表达式树分页扩展(三)​

​C# 表达式树Lambda扩展(四)​

二、表达式树的遍历

要查看表达式树的遍历,肯定不能直接用.Net Framework封装的方法,因为.Net Framework框架是闭源的,除了看中间语言(IL)去查看。我们就用ExpressionVisitor类查看一下他的运行原理,看了下ExpressionVisitor类,里面都是对各个表达式的访问,而且都是虚拟函数,我们可以对他进行override。

ExpressionVisitor类里面都是对各个类型的表达式进行访问,为了更好的理解里面的访问顺序,蜗牛把里面的虚函数都override了一遍,然后跟踪里面的执行顺序。【傻眼了,35个虚函数需要override,内心是很拒绝的,vs2019有没有重写父类虚函数的快捷键啊!!!!!!!】

ExpressionVisitor类相关介绍:​​https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expressionvisitor?view=netframework-4.8​

2.1、ExpressionVisitor类的跟踪

为了不改变ExpressionVisitor类原来的访问,创建的SnailExpressionVisitor.cs 文件只在重写方法里面添加日志打印。

代码如下:

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

public class SnailExpressionVisitor : ExpressionVisitor     {         public override Expression Visit(Expression node)         {             Console.WriteLine($"访问了 Visit,内容:{node.ToString()}");             return base.Visit(node);         }          protected override CatchBlock VisitCatchBlock(CatchBlock node)         {              Console.WriteLine($"访问了 VisitCatchBlock,内容:{node.ToString()}");             return base.VisitCatchBlock(node);         }          protected override ElementInit VisitElementInit(ElementInit node)         {             Console.WriteLine($"访问了 VisitElementInit,内容:{node.ToString()}");             return base.VisitElementInit(node);         }         protected override LabelTarget VisitLabelTarget(LabelTarget node)         {              Console.WriteLine($"访问了 VisitLabelTarget,内容:{node.ToString()}");             return base.VisitLabelTarget(node);         }         protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)         {              Console.WriteLine($"访问了 VisitMemberAssignment,内容:{node.ToString()}");             return base.VisitMemberAssignment(node);         }         protected override MemberBinding VisitMemberBinding(MemberBinding node)         {              Console.WriteLine($"访问了 VisitMemberBinding,内容:{node.ToString()}");             return base.VisitMemberBinding(node);         }          protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)         {              Console.WriteLine($"访问了 VisitMemberListBinding,内容:{node.ToString()}");             return base.VisitMemberListBinding(node);         }         protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)         {              Console.WriteLine($"访问了 VisitMemberMemberBinding,内容:{node.ToString()}");             return base.VisitMemberMemberBinding(node);         }         protected override SwitchCase VisitSwitchCase(SwitchCase node)         {             Console.WriteLine($"访问了 VisitSwitchCase,内容:{node.ToString()}");             return base.VisitSwitchCase(node);         }         protected override Expression VisitBinary(BinaryExpression node)         {             Console.WriteLine($"访问了 VisitBinary,内容:{node.ToString()}");             return base.VisitBinary(node);         }         protected override Expression VisitBlock(BlockExpression node)         {             Console.WriteLine($"访问了 VisitBlock,内容:{node.ToString()}");             return base.VisitBlock(node);         }          protected override Expression VisitConditional(ConditionalExpression node)         {             Console.WriteLine($"访问了 VisitConditional,内容:{node.ToString()}");             return base.VisitConditional(node);         }          protected override Expression VisitConstant(ConstantExpression node)         {             Console.WriteLine($"访问了 VisitConstant,内容:{node.ToString()}");             return base.VisitConstant(node);         }         protected override Expression VisitDebugInfo(DebugInfoExpression node)         {             Console.WriteLine($"访问了 VisitDebugInfo,内容:{node.ToString()}");             return base.VisitDebugInfo(node);         }         protected override Expression VisitDefault(DefaultExpression node)         {             Console.WriteLine($"访问了 VisitDefault,内容:{node.ToString()}");             return base.VisitDefault(node);         }          protected override Expression VisitDynamic(DynamicExpression node)         {             Console.WriteLine($"访问了 VisitDynamic,内容:{node.ToString()}");             return base.VisitDynamic(node);         }         protected override Expression VisitExtension(Expression node)         {             Console.WriteLine($"访问了 VisitExtension,内容:{node.ToString()}");             return base.VisitExtension(node);         }         protected override Expression VisitGoto(GotoExpression node)         {             Console.WriteLine($"访问了 VisitGoto,内容:{node.ToString()}");             return base.VisitGoto(node);         }         protected override Expression VisitIndex(IndexExpression node)         {             Console.WriteLine($"访问了 VisitIndex,内容:{node.ToString()}");             return base.VisitIndex(node);         }         protected override Expression VisitInvocation(InvocationExpression node)         {             Console.WriteLine($"访问了 VisitInvocation,内容:{node.ToString()}");             return base.VisitInvocation(node);         }         protected override Expression VisitLabel(LabelExpression node)         {             Console.WriteLine($"访问了 VisitLabel,内容:{node.ToString()}");             return base.VisitLabel(node);         }         protected override Expression VisitLambda<T>(Expression<T> node)         {             Console.WriteLine($"访问了 VisitLambda,内容:{node.ToString()}");             return base.VisitLambda(node);         }          protected override Expression VisitListInit(ListInitExpression node)         {             Console.WriteLine($"访问了 VisitListInit,内容:{node.ToString()}");             return base.VisitListInit(node);         }         protected override Expression VisitLoop(LoopExpression node)         {             Console.WriteLine($"访问了 VisitLoop,内容:{node.ToString()}");             return base.VisitLoop(node);         }         protected override Expression VisitMember(MemberExpression node)         {             Console.WriteLine($"访问了 VisitMember,内容:{node.ToString()}");             return base.VisitMember(node);         }         protected override Expression VisitMemberInit(MemberInitExpression node)         {             Console.WriteLine($"访问了 VisitMemberInit,内容:{node.ToString()}");             return base.VisitMemberInit(node);         }         protected override Expression VisitMethodCall(MethodCallExpression node)         {             Console.WriteLine($"访问了 VisitMethodCall,内容:{node.ToString()}");             return base.VisitMethodCall(node);         }         protected override Expression VisitNew(NewExpression node)         {             Console.WriteLine($"访问了 VisitNew,内容:{node.ToString()}");             return base.VisitNew(node);         }         protected override Expression VisitNewArray(NewArrayExpression node)         {             Console.WriteLine($"访问了 VisitNewArray,内容:{node.ToString()}");             return base.VisitNewArray(node);         }          protected override Expression VisitParameter(ParameterExpression node)         {             Console.WriteLine($"访问了 VisitParameter,内容:{node.ToString()}");             return base.VisitParameter(node);         }         protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)         {             Console.WriteLine($"访问了 VisitRuntimeVariables,内容:{node.ToString()}");             return base.VisitRuntimeVariables(node);         }          protected override Expression VisitSwitch(SwitchExpression node)         {             Console.WriteLine($"访问了 VisitSwitch,内容:{node.ToString()}");             return base.VisitSwitch(node);         }         protected override Expression VisitTry(TryExpression node)         {             Console.WriteLine($"访问了 VisitTry,内容:{node.ToString()}");             return base.VisitTry(node);         }          protected override Expression VisitTypeBinary(TypeBinaryExpression node)         {             Console.WriteLine($"访问了 VisitTypeBinary,内容:{node.ToString()}");             return base.VisitTypeBinary(node);         }         protected override Expression VisitUnary(UnaryExpression node)         {             Console.WriteLine($"访问了 VisitUnary,内容:{node.ToString()}");             return base.VisitUnary(node);         }        }

View Code


调用方法:


Expression<Func<int, int, bool>> fun = (x, y) => x - y > 5;  var treeModifier = new SnailExpressionVisitor(); Expression modifiedExpr = treeModifier.Visit(fun);


运行结果:

C# 表达式树讲解_c#_13

从打印的日志里面可以看出,

1、每次访问表达式类时,都会先去调用Visit函数,估计他是在Visit里面判定表达式类,然后在根据表达式类的类型,调用访问改表达式的函数

2、对Lambda表达式类,是先访问的是Expression<T>。Expression<T>是不是很熟悉,上一章说过他的作用是将强类型Lambda表达式表示为表达式树形式的数据结构,解析成功之后才对表达式的访问

3、对于表达式先解析的是左边,左边的内容解析完了之后在解析右边,如(x-y)>5,解析的顺序是:x-y=>x=>y=>5

2.2、修改表达式树

既然我们弄清楚了表达式树的访问,现在我们就可以对他进行编辑修改了。

上面我们判断的是x-y>5,现在我们规定,将“-”改成“+”,“>”改成“>=”

对VisitBinary方法修改代码如下:

C# 表达式树讲解_分页_14

protected override Expression VisitBinary(BinaryExpression node) {     Console.WriteLine($"访问了 VisitBinary,内容:{node.ToString()}");     if (node.NodeType == ExpressionType.GreaterThan)     {         Expression left = this.Visit(node.Left);         Expression right = this.Visit(node.Right);          var result = Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, right, node.IsLiftedToNull, node.Method);         Console.WriteLine($"访问了 VisitBinary,更改之后的内容:{result.ToString()}");         return result;     }     else if (node.NodeType == ExpressionType.Subtract || node.NodeType == ExpressionType.SubtractChecked)     {         Expression left = this.Visit(node.Left);         Expression right = this.Visit(node.Right);          var result = Expression.MakeBinary(ExpressionType.Add, left, right, node.IsLiftedToNull, node.Method);         Console.WriteLine($"访问了 VisitBinary,更改之后的内容:{result.ToString()}");         return result;     }     else     {         return base.VisitBinary(node);     } }

C# 表达式树讲解_表达式树_15

调用方法:

C# 表达式树讲解_c#_16

Expression<Func<int, int, bool>> fun = (x, y) => x - y > 5;  var treeModifier = new SnailExpressionVisitor(); Expression modifiedExpr = treeModifier.Visit(fun);  Console.WriteLine($"Lambda的转换最后结果:{modifiedExpr.ToString()}");

C# 表达式树讲解_表达式树_17

运行结果如下

C# 表达式树讲解_ide_18

三、总结

对表达树的讲解已经完成了,但是说了这么久,对真实的开发有什么作用呢?后面我将利用Lambda表达式写一个对现有数据分页的公共方法,同时在对Dapper的扩展也会用到相关知识点,大家拭目以待吧……



出处:javascript:void(0)

=======================================================================================

C# 表达式树分页扩展(三)


一、前言

前面我们知道了表达树的基本知识,也明白了怎么遍历和修改一个表达式,这里我们就一个实际的场景来进行功能开发。

表达式系列目录

​C# 表达式树讲解(一)​

​C# 表达式树遍历(二)​

C# 表达式树分页扩展(三)

​C# 表达式树Lambda扩展(四)​

二、分页扩展

在实际的开发中,肯定会遇到这样的应用场景,一个数据源需要在页面上进行分页显示,并且页面上需要对该数据源有一个排名。本来分页可以在数据库层面完成,但是因为这里有一个排名功能,所谓的排名,就是需要查询出所有满足条件的数据,然后按照某个算法升序或者降序排列,然后按照进行排名。排名之后,然后根据分页信息,将当前页的数据返回给页面,当然中间还有自定义排序的因素。

怎么取数据源和怎么排序,这里先不做介绍,我们就实现对一个数据源分页的功能。

我们先定义好分页的实体对象

分页请求对象PageRequest.cs,因为在【​​ORM之Dapper运用​​】已经说明,所以这里我就只粘贴处代码

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

public class PageRequest     {         /// <summary>         /// 每页条数         /// </summary>         public int PageSize { get; set; }          /// <summary>         /// 当前页数         /// </summary>         public int PageIndex { get; set; }          /// <summary>         /// 排序字段         /// </summary>         public string SortBy { get; set; }          /// <summary>         /// 排序方式(desc、asc)         /// </summary>         public string Direction { get; set; }          /// <summary>         /// 获取排序sql语句         /// </summary>         /// <returns></returns>         public string GetOrderBySql()         {             if (string.IsNullOrWhiteSpace(SortBy))             {                 return "";             }             var resultSql = new StringBuilder(" ORDER BY ");             string dir = Direction;             if (string.IsNullOrWhiteSpace(dir))             {                 dir = "ASC";             }             if (SortBy.Contains("&"))             {                 resultSql.Append("").Append(string.Join(",", SortBy.Split('&').Select(e => $" {e} {dir}").ToArray()));             }             else             {                 resultSql.Append(SortBy).Append("").Append(dir);//默认处理方式             }             return resultSql.ToString();         }     }

View Code

分页的返回对象PageResponse.cs

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

/// <summary>     /// 通用分页返回     /// </summary>     /// <typeparam name="T"></typeparam>     public class PageResponse<T>     {         /// <summary>         /// 总条数         /// </summary>         public long TotalCount { get; set; }          /// <summary>         /// 返回         /// </summary>         public List<T> Items { get; set; }          /// <summary>         /// 当前页         /// </summary>         public long PageIndex { get; set; }          /// <summary>         /// 每页条数         /// </summary>         public long PageSize { get; set; }          /// <summary>         /// 总页数         /// </summary>         public long TotalPages { get; set; }          /// <summary>         /// 返回筛选集合         /// </summary>         public Dictionary<string, List<string>> ResultFilter = new Dictionary<string, List<string>>();      }

View Code

对数据集分页方法的实现

C# 表达式树讲解_分页_23

public class PFTPage     {         /// <summary>         /// 对数据集分页         /// </summary>         /// <typeparam name="T">数据集对象</typeparam>         /// <param name="source">数据集</param>         /// <param name="page">分页信息</param>         /// <returns></returns>         public static PageResponse<T> DataPagination<T>(IQueryable<T> source, PageRequest page) where T : class, new()         {             var sesultData = new PageResponse<T>();             bool isAsc = page.Direction.ToLower() == "asc" ? true : false;             string[] _order = page.SortBy.Split('&');             MethodCallExpression resultExp = null;             foreach (string item in _order)             {                 string _orderPart = item;                 _orderPart = Regex.Replace(_orderPart, @"\s+", "");                 string[] _orderArry = _orderPart.Split(' ');                 string _orderField = _orderArry[0];                 bool sort = isAsc;                 if (_orderArry.Length == 2)                 {                     isAsc = _orderArry[1].ToUpper() == "ASC" ? true : false;                 }                 var parameter = Expression.Parameter(typeof(T), "t");                 var property = typeof(T).GetProperty(_orderField);                 var propertyAccess = Expression.MakeMemberAccess(parameter, property);                 var orderByExp = Expression.Lambda(propertyAccess, parameter);                 resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(T), property.PropertyType }, source.Expression, Expression.Quote(orderByExp));             }             var tempData = source.Provider.CreateQuery<T>(resultExp);             sesultData.PageIndex = page.PageIndex;             sesultData.PageSize = page.PageSize;             sesultData.TotalCount = tempData.Count();              sesultData.TotalPages = sesultData.TotalCount / sesultData.PageSize;             if (sesultData.TotalCount % sesultData.PageSize > 0)             {                 sesultData.TotalPages += 1;             }              sesultData.Items = tempData.Skip(page.PageSize * (page.PageIndex - 1)).Take(page.PageSize).ToList();             return sesultData;         }     }

C# 表达式树讲解_分页_24


为了测试,我们定义一个学生课程成绩的测试类

C# 表达式树讲解_ide_25

public class ScoreClass {     public string CourseName { get; set; }     public string StudentName { get; set; }     public decimal Score { get; set; } }

C# 表达式树讲解_分页_26

调用代码

C# 表达式树讲解_表达式树_27

var datas = new List<ScoreClass>();             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生A",                 Score = 60             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生B",                 Score = 65             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生C",                 Score = 70             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生D",                 Score = 75             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生E",                 Score = 80             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生F",                 Score = 81             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生G",                 Score = 82             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生H",                 Score = 83             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生I",                 Score = 84             });             //按照Score降序排序取第一个(5条数据)             var page = new PageRequest()             {                 Direction= "desc",                 PageIndex=1,                 PageSize=5,                 SortBy= "Score"             };             var result = PFTPage.DataPagination(datas.AsQueryable(), page);              Console.WriteLine($"分页结果:\n{string.Join("\n", result.Items.Select(e=>$"{e.StudentName} {e.CourseName} {e.Score}"))}");

C# 表达式树讲解_ide_28

运行结果

C# 表达式树讲解_ide_29

监控一下result

C# 表达式树讲解_c#_30

返回的都是我们希望返回的数据。

分页公共方法里面,就是根据PageRequest里面的内容,动态的生成表达式树的查询,然后在对数据集使用我们生成的查询表达式树,就返回我们想到的数据集。

三、总结

实现数据分页的公共方法,在后面的遇到数据分页的时候,就会显得非常的方便。有没有感觉很好玩,那么下一篇我们在利用这些知识对Lambda表达式进行扩展。



出处:javascript:void(0)

=======================================================================================

C# 表达式树Lambda扩展(四)


一、前言

本来计算这篇文章在后面需要运用的时候写的,但是既然写到表达式的扩展呢,就一起写完吧。

看到这个标题就有一种疑问,Lambda表达式本来就是表达式树,还需要怎么扩展?那就看看下面的内容,你就知道了。

表达式系列目录

​C# 表达式树讲解(一)​

​C# 表达式树遍历(二)​

​C# 表达式树分页扩展(三)​

C# 表达式树Lambda扩展(四)

二、Lambda扩展

这里先不忙解答上面的问题,我们先看下这样一个应用场景。

一个页面的请求,里面带有一些条件查询,请求类如下


public class ScoreRequest {     public string CourseName { get; set; }     public string StudentName { get; set; } }


要求查询与课程名称和学生名称匹配的数据

数据源我们就以上一例子的数据源

数据源类

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

public class ScoreClass {     public string CourseName { get; set; }     public string StudentName { get; set; }     public decimal Score { get; set; } }

View Code

添加数据

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

var datas = new List<ScoreClass>(); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生A",     Score = 60 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生B",     Score = 65 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生C",     Score = 70 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生D",     Score = 75 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生E",     Score = 80 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生F",     Score = 81 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生G",     Score = 82 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生H",     Score = 83 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生I",     Score = 84 });

View Code

好了现在我们就查询数据

C# 表达式树讲解_表达式树_35

var request = new ScoreRequest()             {                 CourseName = "数",                 StudentName = "H"             };             var resultDatas = datas.Where(e => e.CourseName.Contains(request.CourseName) && e.StudentName.Contains(request.StudentName))                 .ToList();

C# 表达式树讲解_c#_36

如果查询对象里面CourseName和StudentName字段都有值得话,这样写没问题。如果没值,那就最后的数据,就不准确了。

如果是直接拼凑sql语句,我们可以用if(String.IsNullOrEmpty())来判断,但是现在判断了,怎么拼凑Lambda表达式呢?

所以就需要我们对Lambda表达式进行扩展,让他支持这种情况。那上面的问题,就不用再专门回答了吧!!!!

创建一个LambdaExtension的类,代码如下

C# 表达式树讲解_lambda表达式_37

public static class LambdaExtension {     public static Expression<Func<T, bool>> True<T>() { return param => true; }     public static Expression<Func<T, bool>> False<T>() { return param => false; }     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)     {         return first.Compose(second, Expression.AndAlso);     }     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)     {         return first.Compose(second, Expression.OrElse);     }     private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)     {         var map = first.Parameters             .Select((f, i) => new { f, s = second.Parameters[i] })             .ToDictionary(p => p.s, p => p.f);         var secondBody = PFTParameterExtension.ReplaceParameters(map, second.Body);         return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);     }      private class PFTParameterExtension : ExpressionVisitor     {         private readonly Dictionary<ParameterExpression, ParameterExpression> map;          public PFTParameterExtension()         {          }          public PFTParameterExtension(Dictionary<ParameterExpression, ParameterExpression> map)         {             this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();         }          /// <summary>         /// 替换参数         /// </summary>         /// <param name="map">The map.</param>         /// <param name="exp">The exp.</param>         /// <returns>Expression</returns>         public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)         {             return new PFTParameterExtension(map).Visit(exp);         }          protected override Expression VisitParameter(ParameterExpression p)         {             ParameterExpression replacement;             if (map != null && map.Count > 0 && map.TryGetValue(p, out replacement))             {                 p = replacement;             }             return base.VisitParameter(p);         }      }  }

C# 表达式树讲解_c#_38

这里面私有化了一个表达式树访问器,他的作用主要是用来同步Lambda表达式里面的参数。

下面是调用方式

C# 表达式树讲解_c#_39

var expression = LambdaExtension.True<ScoreClass>();             if (!string.IsNullOrWhiteSpace(request.CourseName))                 expression = expression.And(e => e.CourseName.Contains(request.CourseName));             if (!string.IsNullOrWhiteSpace(request.StudentName))                 expression = expression.And(et => et.StudentName.Contains(request.StudentName));              var resultDatas = datas.Where(expression.Compile())                 .ToList();             Console.WriteLine($"查询结果:\n{string.Join("\n", resultDatas.Select(e => $"{e.StudentName} {e.CourseName} {e.Score}"))}");

C# 表达式树讲解_分页_40

where条件里面只能带委托,而我们的expression是Lambda表达式,所以需要Compile进行委托编译。

运行结果:

C# 表达式树讲解_c#_41

仔细看代码,第一个条件And里面的参数是“e”,第二个条件里面的参数是et,同一个Lambda表达式里面(这里只有一个参数),参数肯定是一致的,所以在LambdaExtension类中,在合并两个Lambda表达式的时候,就需要将参数合并成一个。

经过这样的扩展,我们就可以根据我们的实际情况,拼凑好需要的表达式,得到我们想要的结果。

三、总结

表达式树方面的讲解,终于可以告一段落了。一直后没有这样的写文章,现在觉得写文章还是真的挺累的,今年中秋节的这三天,算是全部的给博客园了。不过这三天讲解的内容,基本上把后面Dapper的扩展需要用的技术都铺垫了,后面我们就继续对ORM的讲解了。其实没写一篇博文,蜗牛都会去罗列和梳理相关知识点,这也让蜗牛获益匪浅,也希望蜗牛的博客能帮助到园友,这就是所谓的“赠人玫瑰,手留余香”吧。



出处:javascript:void(0)

=======================================================================================


一、前言

一直想写一篇Dpper的定制化扩展的文章,但是里面会设计到对Lambda表达式的解析,而解析Lambda表达式,就必须要知道表达式树的相关知识点。我希望能通过对各个模块的知识点或者运用能够多一点的讲解,能够帮助到园友了解得更多。虽然讲解得不全面,如果能成为打开这块的一把钥匙,也是蜗牛比较欣慰的。

表达式系列目录

C# 表达式树讲解(一)

​C# 表达式树遍历(二)​

​C# 表达式树分页扩展(三)​

​C# 表达式树Lambda扩展(四)​

二、表达树理解

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,它将我们原来可以直接由代码编写的逻辑以表达式的方式存储在树状的结构里,从而可以在运行时去解析这个树,然后执行,实现动态的编辑和执行代码。在.Net 里面的Linq to SQL就是对表达式树的解析。

这里先讲解下表达式和表达式树,表达式相信大家都知道,比如x+5或者5,都可以算是表达式,而表达式树里面的树指的二叉树,也就是表达式的集合,C#中的Expression类就是表达式类。对于一棵表达式树,其叶子节点都是参数或者常数,非叶子节点都是运算符或者控制符。

2.1、表达式的创建

Lambda表达式方法:


Expression<Func<int, int,bool>> fun = (x, y) => x < y


这种方法创建出的表达式根节点类型为ExpressionType.Lambda,Type类型为返回值类型typeof(bool)

组装法(通过 API 创建表达式树):

C# 表达式树讲解_表达式树_42

ParameterExpression numParam = Expression.Parameter(typeof(int), "num"); ConstantExpression five = Expression.Constant(5, typeof(int)); BinaryExpression numLessThanFive = Expression.LessThan(numParam, five); Expression<Func<int, bool>> lambda1 =     Expression.Lambda<Func<int, bool>>(         numLessThanFive,         new ParameterExpression[] { numParam });

C# 表达式树讲解_ide_43

我们先创建了两个参数表达式num和5,然后用LessThan组装在一起,最终的表达式为“num<5”,expr的节点类型为LessThan,Type类型为typeof(bool)

我们先看看表达式树里面的构造

首先Expression<TDelegate>的功能是将强类型Lambda表达式表示为表达式树形式的数据结构,他的父类是LambdaExpression,比较他们代码可知,Lambda表达式的主体,名称和参数全部保存在LambdaExpression里面。

Expression<TDelegate>与LambdaExpression代码截图:



LambdaExpression里面的Body就是我们的表达式。

C#表达式给我们提供了丰富的表达式类,进入到LambdaExpression类里面


方法返回类型以“Expression”结尾的,基本上都是一个表达式类。

每个表达式代表的定义和创建方法,可以参照微软官方文档​​https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.binaryexpression?view=netframework-4.8​

下面是平常使用最多的表达式

ConstantExpression:常量表达式

ParameterExpression:参数表达式

UnaryExpression:一元运算符表达式

BinaryExpression:二元运算符表达式

TypeBinaryExpression:is运算符表达式

ConditionalExpression:条件表达式

MemberExpression:访问字段或属性表达式

MethodCallExpression:调用成员函数表达式

Expression<TDelegate>:委托表达式

2.2、表达式的解析

表达式树解析

通过LambdaExpression类我们可以知道,表达式树包含:参数[Parameters],表达式树类型[NodeType],表达式[Body],返回类型[ReturnType],Lambda表达式的委托[Compile]以及Lambda表达式名称[name],如图所示:


表达式解析:

所有的表达式都包含:左节点【Left】,右节点【Right】,类型【NodeType】,不同的表达式还会有其他属性,这里的左右节点依旧是表达式。

下图是BinaryExpression表达式截图


表达式树和表达式里面的类型NodeType是一个枚举,一共有85个类型,有兴趣的朋友可以去了解下。

常用的类型如下:

ExpressionType.And:C#中类似于&

ExpressionType.AndAlso:C#中类似于&&

ExpressionType.Or:C#中类似于|

ExpressionType.OrElse:C#中类似于||

ExpressionType.Equal:C#中类似于==

ExpressionType.NotEqual:C#中类似于!=

ExpressionType.GreaterThan:C#中类似于>

ExpressionType.GreaterThanOrEqual:C#中类似于>=

ExpressionType.LessThan:C#中类似于<

ExpressionType.LessThanOrEqual:C#中类似于<=

ExpressionType.Add:C#中类似于+

ExpressionType.AddChecked:C#中类似于+

ExpressionType.Subtract:C#中类似于-

ExpressionType.SubtractChecked:C#中类似于-

ExpressionType.Divide:C#中类似于/

ExpressionType.Multiply:C#中类似于*

ExpressionType.MultiplyChecked:C#中类似于*

2.3、编译表达式树

在表达式创建那,我们组合创建了一个Lambda表达式,那么应该怎么使用它呢?在“表达式的解析”里面,LambdaExpression类和Expression<TDelegate>类都有一个Compile的方法,学名是Lambda表达式的委托,其实就是Lambda表达式编译函数的委托,所以我们只需要调用他,得到的结果就是一个函数方法。

代码修改如下:


C# 表达式树讲解_表达式树_44

ParameterExpression numParam = Expression.Parameter(typeof(int), "num"); ConstantExpression five = Expression.Constant(5, typeof(int)); BinaryExpression numLessThanFive = Expression.LessThan(numParam, five); Expression<Func<int, bool>> lambda1 =     Expression.Lambda<Func<int, bool>>(         numLessThanFive,         new ParameterExpression[] { numParam });  Console.WriteLine($"Lambda的内容:{lambda1.ToString()}");  //表达式的编译 var func = lambda1.Compile(); Console.WriteLine($"Lambda的运行结果:{func(6)}");

C# 表达式树讲解_ide_45

运行结果


三、总结

这里我们对表达式做了基本的讲解,相信大家对Lambda表达式有了初步的了解,下面我们将继续讲解对一个表达式树的遍历。

一、前言

​上一篇​​我们对表达式树有了初步的认识,这里我们将对表达式树进行遍历,只有弄清楚了他的运行原理,我们才可以对他进行定制化修改。

表达式系列目录

​C# 表达式树讲解(一)​

C# 表达式树遍历(二)

​ C# 表达式树分页扩展(三)​

​C# 表达式树Lambda扩展(四)​

二、表达式树的遍历

要查看表达式树的遍历,肯定不能直接用.Net Framework封装的方法,因为.Net Framework框架是闭源的,除了看中间语言(IL)去查看。我们就用ExpressionVisitor类查看一下他的运行原理,看了下ExpressionVisitor类,里面都是对各个表达式的访问,而且都是虚拟函数,我们可以对他进行override。

ExpressionVisitor类里面都是对各个类型的表达式进行访问,为了更好的理解里面的访问顺序,蜗牛把里面的虚函数都override了一遍,然后跟踪里面的执行顺序。【傻眼了,35个虚函数需要override,内心是很拒绝的,vs2019有没有重写父类虚函数的快捷键啊!!!!!!!】

ExpressionVisitor类相关介绍:​​https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.expressions.expressionvisitor?view=netframework-4.8​

2.1、ExpressionVisitor类的跟踪

为了不改变ExpressionVisitor类原来的访问,创建的SnailExpressionVisitor.cs 文件只在重写方法里面添加日志打印。

代码如下:

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

public class SnailExpressionVisitor : ExpressionVisitor     {         public override Expression Visit(Expression node)         {             Console.WriteLine($"访问了 Visit,内容:{node.ToString()}");             return base.Visit(node);         }          protected override CatchBlock VisitCatchBlock(CatchBlock node)         {              Console.WriteLine($"访问了 VisitCatchBlock,内容:{node.ToString()}");             return base.VisitCatchBlock(node);         }          protected override ElementInit VisitElementInit(ElementInit node)         {             Console.WriteLine($"访问了 VisitElementInit,内容:{node.ToString()}");             return base.VisitElementInit(node);         }         protected override LabelTarget VisitLabelTarget(LabelTarget node)         {              Console.WriteLine($"访问了 VisitLabelTarget,内容:{node.ToString()}");             return base.VisitLabelTarget(node);         }         protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)         {              Console.WriteLine($"访问了 VisitMemberAssignment,内容:{node.ToString()}");             return base.VisitMemberAssignment(node);         }         protected override MemberBinding VisitMemberBinding(MemberBinding node)         {              Console.WriteLine($"访问了 VisitMemberBinding,内容:{node.ToString()}");             return base.VisitMemberBinding(node);         }          protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)         {              Console.WriteLine($"访问了 VisitMemberListBinding,内容:{node.ToString()}");             return base.VisitMemberListBinding(node);         }         protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)         {              Console.WriteLine($"访问了 VisitMemberMemberBinding,内容:{node.ToString()}");             return base.VisitMemberMemberBinding(node);         }         protected override SwitchCase VisitSwitchCase(SwitchCase node)         {             Console.WriteLine($"访问了 VisitSwitchCase,内容:{node.ToString()}");             return base.VisitSwitchCase(node);         }         protected override Expression VisitBinary(BinaryExpression node)         {             Console.WriteLine($"访问了 VisitBinary,内容:{node.ToString()}");             return base.VisitBinary(node);         }         protected override Expression VisitBlock(BlockExpression node)         {             Console.WriteLine($"访问了 VisitBlock,内容:{node.ToString()}");             return base.VisitBlock(node);         }          protected override Expression VisitConditional(ConditionalExpression node)         {             Console.WriteLine($"访问了 VisitConditional,内容:{node.ToString()}");             return base.VisitConditional(node);         }          protected override Expression VisitConstant(ConstantExpression node)         {             Console.WriteLine($"访问了 VisitConstant,内容:{node.ToString()}");             return base.VisitConstant(node);         }         protected override Expression VisitDebugInfo(DebugInfoExpression node)         {             Console.WriteLine($"访问了 VisitDebugInfo,内容:{node.ToString()}");             return base.VisitDebugInfo(node);         }         protected override Expression VisitDefault(DefaultExpression node)         {             Console.WriteLine($"访问了 VisitDefault,内容:{node.ToString()}");             return base.VisitDefault(node);         }          protected override Expression VisitDynamic(DynamicExpression node)         {             Console.WriteLine($"访问了 VisitDynamic,内容:{node.ToString()}");             return base.VisitDynamic(node);         }         protected override Expression VisitExtension(Expression node)         {             Console.WriteLine($"访问了 VisitExtension,内容:{node.ToString()}");             return base.VisitExtension(node);         }         protected override Expression VisitGoto(GotoExpression node)         {             Console.WriteLine($"访问了 VisitGoto,内容:{node.ToString()}");             return base.VisitGoto(node);         }         protected override Expression VisitIndex(IndexExpression node)         {             Console.WriteLine($"访问了 VisitIndex,内容:{node.ToString()}");             return base.VisitIndex(node);         }         protected override Expression VisitInvocation(InvocationExpression node)         {             Console.WriteLine($"访问了 VisitInvocation,内容:{node.ToString()}");             return base.VisitInvocation(node);         }         protected override Expression VisitLabel(LabelExpression node)         {             Console.WriteLine($"访问了 VisitLabel,内容:{node.ToString()}");             return base.VisitLabel(node);         }         protected override Expression VisitLambda<T>(Expression<T> node)         {             Console.WriteLine($"访问了 VisitLambda,内容:{node.ToString()}");             return base.VisitLambda(node);         }          protected override Expression VisitListInit(ListInitExpression node)         {             Console.WriteLine($"访问了 VisitListInit,内容:{node.ToString()}");             return base.VisitListInit(node);         }         protected override Expression VisitLoop(LoopExpression node)         {             Console.WriteLine($"访问了 VisitLoop,内容:{node.ToString()}");             return base.VisitLoop(node);         }         protected override Expression VisitMember(MemberExpression node)         {             Console.WriteLine($"访问了 VisitMember,内容:{node.ToString()}");             return base.VisitMember(node);         }         protected override Expression VisitMemberInit(MemberInitExpression node)         {             Console.WriteLine($"访问了 VisitMemberInit,内容:{node.ToString()}");             return base.VisitMemberInit(node);         }         protected override Expression VisitMethodCall(MethodCallExpression node)         {             Console.WriteLine($"访问了 VisitMethodCall,内容:{node.ToString()}");             return base.VisitMethodCall(node);         }         protected override Expression VisitNew(NewExpression node)         {             Console.WriteLine($"访问了 VisitNew,内容:{node.ToString()}");             return base.VisitNew(node);         }         protected override Expression VisitNewArray(NewArrayExpression node)         {             Console.WriteLine($"访问了 VisitNewArray,内容:{node.ToString()}");             return base.VisitNewArray(node);         }          protected override Expression VisitParameter(ParameterExpression node)         {             Console.WriteLine($"访问了 VisitParameter,内容:{node.ToString()}");             return base.VisitParameter(node);         }         protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)         {             Console.WriteLine($"访问了 VisitRuntimeVariables,内容:{node.ToString()}");             return base.VisitRuntimeVariables(node);         }          protected override Expression VisitSwitch(SwitchExpression node)         {             Console.WriteLine($"访问了 VisitSwitch,内容:{node.ToString()}");             return base.VisitSwitch(node);         }         protected override Expression VisitTry(TryExpression node)         {             Console.WriteLine($"访问了 VisitTry,内容:{node.ToString()}");             return base.VisitTry(node);         }          protected override Expression VisitTypeBinary(TypeBinaryExpression node)         {             Console.WriteLine($"访问了 VisitTypeBinary,内容:{node.ToString()}");             return base.VisitTypeBinary(node);         }         protected override Expression VisitUnary(UnaryExpression node)         {             Console.WriteLine($"访问了 VisitUnary,内容:{node.ToString()}");             return base.VisitUnary(node);         }        }

View Code


调用方法:


Expression<Func<int, int, bool>> fun = (x, y) => x - y > 5;  var treeModifier = new SnailExpressionVisitor(); Expression modifiedExpr = treeModifier.Visit(fun);


运行结果:


从打印的日志里面可以看出,

1、每次访问表达式类时,都会先去调用Visit函数,估计他是在Visit里面判定表达式类,然后在根据表达式类的类型,调用访问改表达式的函数

2、对Lambda表达式类,是先访问的是Expression<T>。Expression<T>是不是很熟悉,上一章说过他的作用是将强类型Lambda表达式表示为表达式树形式的数据结构,解析成功之后才对表达式的访问

3、对于表达式先解析的是左边,左边的内容解析完了之后在解析右边,如(x-y)>5,解析的顺序是:x-y=>x=>y=>5

2.2、修改表达式树

既然我们弄清楚了表达式树的访问,现在我们就可以对他进行编辑修改了。

上面我们判断的是x-y>5,现在我们规定,将“-”改成“+”,“>”改成“>=”

对VisitBinary方法修改代码如下:

C# 表达式树讲解_表达式树_48

protected override Expression VisitBinary(BinaryExpression node) {     Console.WriteLine($"访问了 VisitBinary,内容:{node.ToString()}");     if (node.NodeType == ExpressionType.GreaterThan)     {         Expression left = this.Visit(node.Left);         Expression right = this.Visit(node.Right);          var result = Expression.MakeBinary(ExpressionType.GreaterThanOrEqual, left, right, node.IsLiftedToNull, node.Method);         Console.WriteLine($"访问了 VisitBinary,更改之后的内容:{result.ToString()}");         return result;     }     else if (node.NodeType == ExpressionType.Subtract || node.NodeType == ExpressionType.SubtractChecked)     {         Expression left = this.Visit(node.Left);         Expression right = this.Visit(node.Right);          var result = Expression.MakeBinary(ExpressionType.Add, left, right, node.IsLiftedToNull, node.Method);         Console.WriteLine($"访问了 VisitBinary,更改之后的内容:{result.ToString()}");         return result;     }     else     {         return base.VisitBinary(node);     } }

C# 表达式树讲解_c#_49

调用方法:

C# 表达式树讲解_分页_50

Expression<Func<int, int, bool>> fun = (x, y) => x - y > 5;  var treeModifier = new SnailExpressionVisitor(); Expression modifiedExpr = treeModifier.Visit(fun);  Console.WriteLine($"Lambda的转换最后结果:{modifiedExpr.ToString()}");

C# 表达式树讲解_c#_51

运行结果如下


三、总结

对表达树的讲解已经完成了,但是说了这么久,对真实的开发有什么作用呢?后面我将利用Lambda表达式写一个对现有数据分页的公共方法,同时在对Dapper的扩展也会用到相关知识点,大家拭目以待吧……

一、前言

前面我们知道了表达树的基本知识,也明白了怎么遍历和修改一个表达式,这里我们就一个实际的场景来进行功能开发。

表达式系列目录

​C# 表达式树讲解(一)​

​C# 表达式树遍历(二)​

C# 表达式树分页扩展(三)

​C# 表达式树Lambda扩展(四)​

二、分页扩展

在实际的开发中,肯定会遇到这样的应用场景,一个数据源需要在页面上进行分页显示,并且页面上需要对该数据源有一个排名。本来分页可以在数据库层面完成,但是因为这里有一个排名功能,所谓的排名,就是需要查询出所有满足条件的数据,然后按照某个算法升序或者降序排列,然后按照进行排名。排名之后,然后根据分页信息,将当前页的数据返回给页面,当然中间还有自定义排序的因素。

怎么取数据源和怎么排序,这里先不做介绍,我们就实现对一个数据源分页的功能。

我们先定义好分页的实体对象

分页请求对象PageRequest.cs,因为在【​​ORM之Dapper运用​​】已经说明,所以这里我就只粘贴处代码

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

public class PageRequest     {         /// <summary>         /// 每页条数         /// </summary>         public int PageSize { get; set; }          /// <summary>         /// 当前页数         /// </summary>         public int PageIndex { get; set; }          /// <summary>         /// 排序字段         /// </summary>         public string SortBy { get; set; }          /// <summary>         /// 排序方式(desc、asc)         /// </summary>         public string Direction { get; set; }          /// <summary>         /// 获取排序sql语句         /// </summary>         /// <returns></returns>         public string GetOrderBySql()         {             if (string.IsNullOrWhiteSpace(SortBy))             {                 return "";             }             var resultSql = new StringBuilder(" ORDER BY ");             string dir = Direction;             if (string.IsNullOrWhiteSpace(dir))             {                 dir = "ASC";             }             if (SortBy.Contains("&"))             {                 resultSql.Append("").Append(string.Join(",", SortBy.Split('&').Select(e => $" {e} {dir}").ToArray()));             }             else             {                 resultSql.Append(SortBy).Append("").Append(dir);//默认处理方式             }             return resultSql.ToString();         }     }

View Code

分页的返回对象PageResponse.cs

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

/// <summary>     /// 通用分页返回     /// </summary>     /// <typeparam name="T"></typeparam>     public class PageResponse<T>     {         /// <summary>         /// 总条数         /// </summary>         public long TotalCount { get; set; }          /// <summary>         /// 返回         /// </summary>         public List<T> Items { get; set; }          /// <summary>         /// 当前页         /// </summary>         public long PageIndex { get; set; }          /// <summary>         /// 每页条数         /// </summary>         public long PageSize { get; set; }          /// <summary>         /// 总页数         /// </summary>         public long TotalPages { get; set; }          /// <summary>         /// 返回筛选集合         /// </summary>         public Dictionary<string, List<string>> ResultFilter = new Dictionary<string, List<string>>();      }

View Code

对数据集分页方法的实现

C# 表达式树讲解_分页_56

public class PFTPage     {         /// <summary>         /// 对数据集分页         /// </summary>         /// <typeparam name="T">数据集对象</typeparam>         /// <param name="source">数据集</param>         /// <param name="page">分页信息</param>         /// <returns></returns>         public static PageResponse<T> DataPagination<T>(IQueryable<T> source, PageRequest page) where T : class, new()         {             var sesultData = new PageResponse<T>();             bool isAsc = page.Direction.ToLower() == "asc" ? true : false;             string[] _order = page.SortBy.Split('&');             MethodCallExpression resultExp = null;             foreach (string item in _order)             {                 string _orderPart = item;                 _orderPart = Regex.Replace(_orderPart, @"\s+", "");                 string[] _orderArry = _orderPart.Split(' ');                 string _orderField = _orderArry[0];                 bool sort = isAsc;                 if (_orderArry.Length == 2)                 {                     isAsc = _orderArry[1].ToUpper() == "ASC" ? true : false;                 }                 var parameter = Expression.Parameter(typeof(T), "t");                 var property = typeof(T).GetProperty(_orderField);                 var propertyAccess = Expression.MakeMemberAccess(parameter, property);                 var orderByExp = Expression.Lambda(propertyAccess, parameter);                 resultExp = Expression.Call(typeof(Queryable), isAsc ? "OrderBy" : "OrderByDescending", new Type[] { typeof(T), property.PropertyType }, source.Expression, Expression.Quote(orderByExp));             }             var tempData = source.Provider.CreateQuery<T>(resultExp);             sesultData.PageIndex = page.PageIndex;             sesultData.PageSize = page.PageSize;             sesultData.TotalCount = tempData.Count();              sesultData.TotalPages = sesultData.TotalCount / sesultData.PageSize;             if (sesultData.TotalCount % sesultData.PageSize > 0)             {                 sesultData.TotalPages += 1;             }              sesultData.Items = tempData.Skip(page.PageSize * (page.PageIndex - 1)).Take(page.PageSize).ToList();             return sesultData;         }     }

C# 表达式树讲解_ide_57


为了测试,我们定义一个学生课程成绩的测试类

C# 表达式树讲解_ide_58

public class ScoreClass {     public string CourseName { get; set; }     public string StudentName { get; set; }     public decimal Score { get; set; } }

C# 表达式树讲解_分页_59

调用代码

C# 表达式树讲解_c#_60

var datas = new List<ScoreClass>();             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生A",                 Score = 60             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生B",                 Score = 65             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生C",                 Score = 70             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生D",                 Score = 75             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生E",                 Score = 80             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生F",                 Score = 81             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生G",                 Score = 82             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生H",                 Score = 83             });             datas.Add(new ScoreClass             {                 CourseName = "数学",                 StudentName = "学生I",                 Score = 84             });             //按照Score降序排序取第一个(5条数据)             var page = new PageRequest()             {                 Direction= "desc",                 PageIndex=1,                 PageSize=5,                 SortBy= "Score"             };             var result = PFTPage.DataPagination(datas.AsQueryable(), page);              Console.WriteLine($"分页结果:\n{string.Join("\n", result.Items.Select(e=>$"{e.StudentName} {e.CourseName} {e.Score}"))}");

C# 表达式树讲解_c#_61

运行结果


监控一下result


返回的都是我们希望返回的数据。

分页公共方法里面,就是根据PageRequest里面的内容,动态的生成表达式树的查询,然后在对数据集使用我们生成的查询表达式树,就返回我们想到的数据集。

三、总结

实现数据分页的公共方法,在后面的遇到数据分页的时候,就会显得非常的方便。有没有感觉很好玩,那么下一篇我们在利用这些知识对Lambda表达式进行扩展。

一、前言

本来计算这篇文章在后面需要运用的时候写的,但是既然写到表达式的扩展呢,就一起写完吧。

看到这个标题就有一种疑问,Lambda表达式本来就是表达式树,还需要怎么扩展?那就看看下面的内容,你就知道了。

表达式系列目录

​C# 表达式树讲解(一)​

​C# 表达式树遍历(二)​

​C# 表达式树分页扩展(三)​

C# 表达式树Lambda扩展(四)

二、Lambda扩展

这里先不忙解答上面的问题,我们先看下这样一个应用场景。

一个页面的请求,里面带有一些条件查询,请求类如下


public class ScoreRequest {     public string CourseName { get; set; }     public string StudentName { get; set; } }


要求查询与课程名称和学生名称匹配的数据

数据源我们就以上一例子的数据源

数据源类

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

public class ScoreClass {     public string CourseName { get; set; }     public string StudentName { get; set; }     public decimal Score { get; set; } }

View Code

添加数据

C# 表达式树讲解_表达式树_11C# 表达式树讲解_c#_12

var datas = new List<ScoreClass>(); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生A",     Score = 60 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生B",     Score = 65 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生C",     Score = 70 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生D",     Score = 75 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生E",     Score = 80 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生F",     Score = 81 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生G",     Score = 82 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生H",     Score = 83 }); datas.Add(new ScoreClass {     CourseName = "数学",     StudentName = "学生I",     Score = 84 });

View Code

好了现在我们就查询数据

C# 表达式树讲解_lambda表达式_66

var request = new ScoreRequest()             {                 CourseName = "数",                 StudentName = "H"             };             var resultDatas = datas.Where(e => e.CourseName.Contains(request.CourseName) && e.StudentName.Contains(request.StudentName))                 .ToList();

C# 表达式树讲解_c#_67

如果查询对象里面CourseName和StudentName字段都有值得话,这样写没问题。如果没值,那就最后的数据,就不准确了。

如果是直接拼凑sql语句,我们可以用if(String.IsNullOrEmpty())来判断,但是现在判断了,怎么拼凑Lambda表达式呢?

所以就需要我们对Lambda表达式进行扩展,让他支持这种情况。那上面的问题,就不用再专门回答了吧!!!!

创建一个LambdaExtension的类,代码如下

C# 表达式树讲解_分页_68

public static class LambdaExtension {     public static Expression<Func<T, bool>> True<T>() { return param => true; }     public static Expression<Func<T, bool>> False<T>() { return param => false; }     public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)     {         return first.Compose(second, Expression.AndAlso);     }     public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)     {         return first.Compose(second, Expression.OrElse);     }     private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)     {         var map = first.Parameters             .Select((f, i) => new { f, s = second.Parameters[i] })             .ToDictionary(p => p.s, p => p.f);         var secondBody = PFTParameterExtension.ReplaceParameters(map, second.Body);         return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);     }      private class PFTParameterExtension : ExpressionVisitor     {         private readonly Dictionary<ParameterExpression, ParameterExpression> map;          public PFTParameterExtension()         {          }          public PFTParameterExtension(Dictionary<ParameterExpression, ParameterExpression> map)         {             this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();         }          /// <summary>         /// 替换参数         /// </summary>         /// <param name="map">The map.</param>         /// <param name="exp">The exp.</param>         /// <returns>Expression</returns>         public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)         {             return new PFTParameterExtension(map).Visit(exp);         }          protected override Expression VisitParameter(ParameterExpression p)         {             ParameterExpression replacement;             if (map != null && map.Count > 0 && map.TryGetValue(p, out replacement))             {                 p = replacement;             }             return base.VisitParameter(p);         }      }  }

C# 表达式树讲解_分页_69

这里面私有化了一个表达式树访问器,他的作用主要是用来同步Lambda表达式里面的参数。

下面是调用方式

C# 表达式树讲解_lambda表达式_70

var expression = LambdaExtension.True<ScoreClass>();             if (!string.IsNullOrWhiteSpace(request.CourseName))                 expression = expression.And(e => e.CourseName.Contains(request.CourseName));             if (!string.IsNullOrWhiteSpace(request.StudentName))                 expression = expression.And(et => et.StudentName.Contains(request.StudentName));              var resultDatas = datas.Where(expression.Compile())                 .ToList();             Console.WriteLine($"查询结果:\n{string.Join("\n", resultDatas.Select(e => $"{e.StudentName} {e.CourseName} {e.Score}"))}");

C# 表达式树讲解_c#_71

where条件里面只能带委托,而我们的expression是Lambda表达式,所以需要Compile进行委托编译。

运行结果:


仔细看代码,第一个条件And里面的参数是“e”,第二个条件里面的参数是et,同一个Lambda表达式里面(这里只有一个参数),参数肯定是一致的,所以在LambdaExtension类中,在合并两个Lambda表达式的时候,就需要将参数合并成一个。

经过这样的扩展,我们就可以根据我们的实际情况,拼凑好需要的表达式,得到我们想要的结果。

三、总结

表达式树方面的讲解,终于可以告一段落了。一直后没有这样的写文章,现在觉得写文章还是真的挺累的,今年中秋节的这三天,算是全部的给博客园了。不过这三天讲解的内容,基本上把后面Dapper的扩展需要用的技术都铺垫了,后面我们就继续对ORM的讲解了。其实没写一篇博文,蜗牛都会去罗列和梳理相关知识点,这也让蜗牛获益匪浅,也希望蜗牛的博客能帮助到园友,这就是所谓的“赠人玫瑰,手留余香”吧。