C# 表達式樹講解(一)
一、前言
一直想寫一篇Dpper的定制化擴展的文章,但是里面會設計到對Lambda表達式的解析,而解析Lambda表達式,就必須要知道表達式樹的相關知識點。我希望能通過對各個模塊的知識點或者運用能夠多一點的講解,能夠幫助到園友了解得更多。雖然講解得不全面,如果能成為打開這塊的一把鑰匙,也是蝸牛比較欣慰的。
表達式系列目錄
C# 表達式樹講解(一)
二、表達樹理解
表達式樹以樹形數據結構表示代碼,其中每一個節點都是一種表達式,它將我們原來可以直接由代碼編寫的邏輯以表達式的方式存儲在樹狀的結構里,從而可以在運行時去解析這個樹,然后執行,實現動態的編輯和執行代碼。在.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 創建表達式樹):
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 });
我們先創建了兩個參數表達式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表達式編譯函數的委托,所以我們只需要調用他,得到的結果就是一個函數方法。
代碼修改如下:
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)}");
運行結果
三、總結
這里我們對表達式做了基本的講解,相信大家對Lambda表達式有了初步的了解,下面我們將繼續講解對一個表達式樹的遍歷。
出處:https://www.cnblogs.com/snailblog/p/11521043.html
=======================================================================================
C# 表達式樹遍歷(二)
一、前言
上一篇我們對表達式樹有了初步的認識,這里我們將對表達式樹進行遍歷,只有弄清楚了他的運行原理,我們才可以對他進行定制化修改。
表達式系列目錄
C# 表達式樹遍歷(二)
二、表達式樹的遍歷
要查看表達式樹的遍歷,肯定不能直接用.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 文件只在重寫方法里面添加日志打印。
代碼如下:

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); } }
調用方法:
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方法修改代碼如下:
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); } }
調用方法:
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()}");
運行結果如下
三、總結
對表達樹的講解已經完成了,但是說了這么久,對真實的開發有什么作用呢?后面我將利用Lambda表達式寫一個對現有數據分頁的公共方法,同時在對Dapper的擴展也會用到相關知識點,大家拭目以待吧……
出處:https://www.cnblogs.com/snailblog/p/11521335.html
=======================================================================================
C# 表達式樹分頁擴展(三)
一、前言
前面我們知道了表達樹的基本知識,也明白了怎么遍歷和修改一個表達式,這里我們就一個實際的場景來進行功能開發。
表達式系列目錄
C# 表達式樹分頁擴展(三)
二、分頁擴展
在實際的開發中,肯定會遇到這樣的應用場景,一個數據源需要在頁面上進行分頁顯示,並且頁面上需要對該數據源有一個排名。本來分頁可以在數據庫層面完成,但是因為這里有一個排名功能,所謂的排名,就是需要查詢出所有滿足條件的數據,然后按照某個算法升序或者降序排列,然后按照進行排名。排名之后,然后根據分頁信息,將當前頁的數據返回給頁面,當然中間還有自定義排序的因素。
怎么取數據源和怎么排序,這里先不做介紹,我們就實現對一個數據源分頁的功能。
我們先定義好分頁的實體對象
分頁請求對象PageRequest.cs,因為在【ORM之Dapper運用】已經說明,所以這里我就只粘貼處代碼

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(); } }
分頁的返回對象PageResponse.cs

/// <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>>(); }
對數據集分頁方法的實現
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; } }
為了測試,我們定義一個學生課程成績的測試類
public class ScoreClass { public string CourseName { get; set; } public string StudentName { get; set; } public decimal Score { get; set; } }
調用代碼
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}"))}");
運行結果
監控一下result
返回的都是我們希望返回的數據。
分頁公共方法里面,就是根據PageRequest里面的內容,動態的生成表達式樹的查詢,然后在對數據集使用我們生成的查詢表達式樹,就返回我們想到的數據集。
三、總結
實現數據分頁的公共方法,在后面的遇到數據分頁的時候,就會顯得非常的方便。有沒有感覺很好玩,那么下一篇我們在利用這些知識對Lambda表達式進行擴展。
出處:https://www.cnblogs.com/snailblog/p/11521359.html
=======================================================================================
C# 表達式樹Lambda擴展(四)
一、前言
本來計算這篇文章在后面需要運用的時候寫的,但是既然寫到表達式的擴展呢,就一起寫完吧。
看到這個標題就有一種疑問,Lambda表達式本來就是表達式樹,還需要怎么擴展?那就看看下面的內容,你就知道了。
表達式系列目錄
C# 表達式樹Lambda擴展(四)
二、Lambda擴展
這里先不忙解答上面的問題,我們先看下這樣一個應用場景。
一個頁面的請求,里面帶有一些條件查詢,請求類如下
public class ScoreRequest { public string CourseName { get; set; } public string StudentName { get; set; } }
要求查詢與課程名稱和學生名稱匹配的數據
數據源我們就以上一例子的數據源
數據源類

public class ScoreClass { public string CourseName { get; set; } public string StudentName { get; set; } public decimal Score { get; set; } }
添加數據

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 });
好了現在我們就查詢數據
var request = new ScoreRequest() { CourseName = "數", StudentName = "H" }; var resultDatas = datas.Where(e => e.CourseName.Contains(request.CourseName) && e.StudentName.Contains(request.StudentName)) .ToList();
如果查詢對象里面CourseName和StudentName字段都有值得話,這樣寫沒問題。如果沒值,那就最后的數據,就不准確了。
如果是直接拼湊sql語句,我們可以用if(String.IsNullOrEmpty())來判斷,但是現在判斷了,怎么拼湊Lambda表達式呢?
所以就需要我們對Lambda表達式進行擴展,讓他支持這種情況。那上面的問題,就不用再專門回答了吧!!!!
創建一個LambdaExtension的類,代碼如下
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); } } }
這里面私有化了一個表達式樹訪問器,他的作用主要是用來同步Lambda表達式里面的參數。
下面是調用方式
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}"))}");
where條件里面只能帶委托,而我們的expression是Lambda表達式,所以需要Compile進行委托編譯。
運行結果:
仔細看代碼,第一個條件And里面的參數是“e”,第二個條件里面的參數是et,同一個Lambda表達式里面(這里只有一個參數),參數肯定是一致的,所以在LambdaExtension類中,在合並兩個Lambda表達式的時候,就需要將參數合並成一個。
經過這樣的擴展,我們就可以根據我們的實際情況,拼湊好需要的表達式,得到我們想要的結果。
三、總結
表達式樹方面的講解,終於可以告一段落了。一直后沒有這樣的寫文章,現在覺得寫文章還是真的挺累的,今年中秋節的這三天,算是全部的給博客園了。不過這三天講解的內容,基本上把后面Dapper的擴展需要用的技術都鋪墊了,后面我們就繼續對ORM的講解了。其實沒寫一篇博文,蝸牛都會去羅列和梳理相關知識點,這也讓蝸牛獲益匪淺,也希望蝸牛的博客能幫助到園友,這就是所謂的“贈人玫瑰,手留余香”吧。
出處:https://www.cnblogs.com/snailblog/p/11525118.html
=======================================================================================