日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

再讲IQueryablelt;Tgt;,揭开表达式树的神秘面纱

發布時間:2023/12/4 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 再讲IQueryablelt;Tgt;,揭开表达式树的神秘面纱 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

接上篇《先說IEnumerable,我們每天用的foreach你真的懂它嗎?》

最近園子里定制自己的orm那是一個風生水起,感覺不整個自己的orm都不好意思繼續混博客園了(開個玩笑)。那么在此之前我們有必要仔細了解下?IQueryable<T>?,于是就有了此文。

什么是樹?

什么是樹?這個問題好像有點白癡。樹不就是樹嘛。看圖:

我們從最下面的主干開始往上看,主枝-分支-分支....可以說是無限分支下去。我們倒過來看就是這樣:

平時我們用得最多的樹結構數據就是XML了,節點下面可以無限添加子節點。我們想想平時還用過什么樹結構數據,比如:菜單無限分級、評論區的樓層。

這和我們今天講的有毛關系啊。... 我們今天主要就是來分析表達式樹的。、

lambda表達式和表達式樹的區別:

Lambda表達式:

Func<Student, bool> func = t => t.Name == "農碼一生";

表達式樹:?

Expression<Func<Student, bool>> expression = t => t.Name == "農碼一生";

?咋一看,沒啥區別啊。表達式只是用Expression包了一下而已。那你錯了,這只是Microsoft給我們展示的障眼法,我們看編譯后的C#代碼:

第一個lambda表達式編譯成了匿名函數,第二個表達式樹編譯成一了一堆我們不認識的東西,遠比我們原來寫的lambda復雜得多。

結論:

  • 我們平時使用的表達式樹,是編寫的lambda表達式然后編譯成的表達式樹,也就是說平時一般情況使用的表達式樹都是編譯器幫我們完成的。(當然,我們可以可以手動的主動的去創表達式樹。只是太麻煩,不是必要情況沒有誰愿意去干這個苦活呢

我們來看看表達式樹到底有什么神奇的地方:

有沒有看出點感覺來?Body里面有Right、Left,Right里面又有Right、Left,它們的類型都是繼承自?Expression?。這種節點下面有節點,可以無限附加下去的數據結構我們稱為樹結構數據。也就是我們的表達式樹。

補:上面的?Student?實體類:

public class Student

{

? ? public string Name { get; set; }


? ? public int Age { get; set; }


? ? public string Address { get; set; }


? ? public string Sex { get; set; }

}

解析表達式樹

上面我們看到了所謂的表達式樹,其他也沒有想象的那么復雜嘛。不就是一個樹結構數據嘛。如果我們要實現自己的orm,免不了要解析表達式樹。一般說到解析樹結構數據都會用到遞歸算法。下面我們開始解析表達式樹。

先定義解析方法:

//表達式解析

public static class AnalysisExpression

{

? ? public static void VisitExpression(Expression expression)

? ? {

? ? ? ? switch (expression.NodeType)

? ? ? ? {

? ? ? ? ? ? case ExpressionType.Call://執行方法

? ? ? ? ? ? ? ? MethodCallExpression method = expression as MethodCallExpression;

? ? ? ? ? ? ? ? Console.WriteLine("方法名:" + method.Method.Name);

? ? ? ? ? ? ? ? for (int i = 0; i < method.Arguments.Count; i++)

? ? ? ? ? ? ? ? ? ? VisitExpression(method.Arguments[i]);

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case ExpressionType.Lambda://lambda表達式

? ? ? ? ? ? ? ? LambdaExpression lambda = expression as LambdaExpression;

? ? ? ? ? ? ? ? VisitExpression(lambda.Body);

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case ExpressionType.Equal://相等比較

? ? ? ? ? ? case ExpressionType.AndAlso://and條件運算

? ? ? ? ? ? ? ? BinaryExpression binary = expression as BinaryExpression;

? ? ? ? ? ? ? ? Console.WriteLine("運算符:" + expression.NodeType.ToString());

? ? ? ? ? ? ? ? VisitExpression(binary.Left);

? ? ? ? ? ? ? ? VisitExpression(binary.Right);

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case ExpressionType.Constant://常量值

? ? ? ? ? ? ? ? ConstantExpression constant = expression as ConstantExpression;

? ? ? ? ? ? ? ? Console.WriteLine("常量值:" + constant.Value.ToString());

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case ExpressionType.MemberAccess:

? ? ? ? ? ? ? ? MemberExpression Member = expression as MemberExpression;

? ? ? ? ? ? ? ? Console.WriteLine("字段名稱:{0},類型:{1}", Member.Member.Name, Member.Type.ToString());

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? default:

? ? ? ? ? ? ? ? Console.Write("UnKnow");

? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? }


}

調用解析方法:

Expression<Func<Student, bool>> expression = t => t.Name == "農碼一生" && t.Sex == ""; AnalysisExpression.VisitExpression(expression);

我們來看看執行過程:

一層一層的往子節點遞歸,直到遍歷完所有的節點。最后打印效果如下:

基本上我們想要的元素和值都取到了,接著怎么組裝就看你自己的心情了。是拼成sql,還是生成url,請隨意!

實現自己的IQueryable<T>、IQueryProvider

僅僅解析了表達式樹就可以搗鼓自己的orm了?不行,起碼也要基于?IQueryable<T>?接口來編碼吧。

接著我們自定義個類?MyQueryable<T>?繼承接口?IQueryable<T>?:

public class MyQueryable<T> : IQueryable<T>

?{

? ? ?public IEnumerator<T> GetEnumerator()

? ? ?{

? ? ? ? ?throw new NotImplementedException();

? ? ?}

? ? ?IEnumerator IEnumerable.GetEnumerator()

? ? ?{

? ? ? ? ?throw new NotImplementedException();

? ? ?}

? ? ?public Type ElementType

? ? ?{

? ? ? ? ?get { throw new NotImplementedException(); }

? ? ?}

? ? ?public Expression Expression

? ? ?{

? ? ? ? ?get { throw new NotImplementedException(); }

? ? ?}

? ? ?public IQueryProvider Provider

? ? ?{

? ? ? ? ?get { throw new NotImplementedException(); }

? ? ?}

?}

我們看到其中有個接口屬性?IQueryProvider?,這個接口的作用大著呢,主要作用是在執行查詢操作符的時候重新創建?IQueryable<T>?并且最后遍歷的時候執行sql遠程取值。我們還看見了?Expression??屬性。

現在我們明白了?IQueryable<T>?和?Expression?(表達式樹)的關系了吧:

  • ?IQueryable<T>?最主要的作用就是用來存儲?Expression(表達式樹)

下面我們也自定義現實了?IQueryProvider?接口的類?MyQueryProvider?:

public class MyQueryProvider : IQueryProvider

{

? ? public IQueryable<TElement> CreateQuery<TElement>(Expression expression)

? ? {

? ? ? ? throw new NotImplementedException();

? ? }

? ? public IQueryable CreateQuery(Expression expression)

? ? {

? ? ? ? throw new NotImplementedException();

? ? }

? ? public TResult Execute<TResult>(Expression expression)

? ? {

? ? ? ? throw new NotImplementedException();

? ? }

? ? public object Execute(Expression expression)

? ? {

? ? ? ? throw new NotImplementedException();

? ? }

}

上面全是自動生成的偽代碼,下面我們來填充具體的實現:

public class MyQueryProvider : IQueryProvider

? ? {

? ? ? ? public IQueryable<TElement> CreateQuery<TElement>(Expression expression)

? ? ? ? {

? ? ? ? ? ? return new MyQueryable<TElement>(expression);

? ? ? ? }


? ? ? ? public IQueryable CreateQuery(Expression expression)

? ? ? ? {

? ? ? ? ? ? throw new NotImplementedException();

? ? ? ? }


? ? ? ? public TResult Execute<TResult>(Expression expression)

? ? ? ? {

? ? ? ? ? ? return default(TResult);

? ? ? ? }


? ? ? ? public object Execute(Expression expression)

? ? ? ? {

? ? ? ? ? ? return new List<object>();

? ? ? ? }?

? ? } ?

? ? public class MyQueryable<T> : IQueryable<T>

? ? {

? ? ? ? public MyQueryable()

? ? ? ? {

? ? ? ? ? ? _provider = new MyQueryProvider();

? ? ? ? ? ? _expression = Expression.Constant(this);

? ? ? ? }


? ? ? ? public MyQueryable(Expression expression)

? ? ? ? {

? ? ? ? ? ? _provider = new MyQueryProvider();

? ? ? ? ? ? _expression = expression;

? ? ? ? }

? ? ? ? public Type ElementType

? ? ? ? {

? ? ? ? ? ? get { return typeof(T); }

? ? ? ? }


? ? ? ? private Expression _expression;

? ? ? ? public Expression Expression

? ? ? ? {

? ? ? ? ? ? get { return _expression; }

? ? ? ? }


? ? ? ? private IQueryProvider _provider;

? ? ? ? public IQueryProvider Provider

? ? ? ? {

? ? ? ? ? ? get { return _provider; }

? ? ? ? }


? ? ? ? public IEnumerator GetEnumerator()

? ? ? ? {

? ? ? ? ? ? return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();

? ? ? ? }


? ? ? ? IEnumerator<T> IEnumerable<T>.GetEnumerator()

? ? ? ? {

? ? ? ? ? ? var result = _provider.Execute<List<T>>(_expression);

? ? ? ? ? ? if (result == null)

? ? ? ? ? ? ? ? yield break;

? ? ? ? ? ? foreach (var item in result)

? ? ? ? ? ? {

? ? ? ? ? ? ? ? yield return item;

? ? ? ? ? ? }

? ? ? ? }

? ? }

執行代碼:

var aa = new MyQueryable<Student>(); var bb = aa.Where(t => t.Name == "農碼一生"); var cc = bb.Where(t => t.Sex == ""); var dd = cc.AsEnumerable(); var ee = cc.ToList();

接著我們看看執行過程:

結論:

  • 每次在執行?Where?查詢操作符的時候?IQueryProvider?會為我們創建一個新的?IQueryable<T>?

  • 調用?AsEnumerable()?方法的時候并不會去實際取值(只是得到一個IEnumerable)[注意:在EF里面查詢不要先取IEnumerable后濾篩,因為AsEnumerable()會生成查詢全表的sql]

  • 執行?ToList()?方法時才去真正調用迭代器?GetEnumerator()?取值

  • 真正取值的時候,會去執行?IQueryProvider?中的?Execute?方法。(就是在調用這個方法的時候解析表達式數,然后執行取得結果)

我們看到真正應該辦實事的?Execute??我們卻讓他返回默認值了。

現在估計有人不爽了,你到是具體實現下?Execute?。好吧!(其實通過上面說的解析表達式樹,你可以自己在這里做想做的任何事了。)

首先為了簡單起見,我們用一個集合做為數據源:

//構造Student數組

public static List<Student> StudentArrary = new List<Student>()

{

? ? ? ? new Student(){Name="農碼一生", Age=26, Sex="男", Address="長沙"},

? ? ? ? new Student(){Name="小明", Age=23, Sex="男", Address="岳陽"},

? ? ? ? new Student(){Name="嗨-妹子", Age=25, Sex="女", Address="四川"}

};

然后,重新寫一個VisitExpression2方法:(和之前的區別: 現在目的是取表達式樹中的表達式,而不是重新組裝成sql或別的)

public static void VisitExpression2(Expression expression, ref List<LambdaExpression> lambdaOut)

{

? ? if (lambdaOut == null)

? ? ? ? lambdaOut = new List<LambdaExpression>();

? ? switch (expression.NodeType)

? ? {

? ? ? ? case ExpressionType.Call://執行方法

? ? ? ? ? ? MethodCallExpression method = expression as MethodCallExpression;

? ? ? ? ? ? Console.WriteLine("方法名:" + method.Method.Name);

? ? ? ? ? ? for (int i = 0; i < method.Arguments.Count; i++)

? ? ? ? ? ? ? ? VisitExpression2(method.Arguments[i], ref ?lambdaOut);

? ? ? ? ? ? break;

? ? ? ? case ExpressionType.Lambda://lambda表達式

? ? ? ? ? ? LambdaExpression lambda = expression as LambdaExpression;

? ? ? ? ? ? lambdaOut.Add(lambda);

? ? ? ? ? ? VisitExpression2(lambda.Body, ref ?lambdaOut);

? ? ? ? ? ? break;

? ? ? ? case ExpressionType.Equal://相等比較

? ? ? ? case ExpressionType.AndAlso://and條件運算

? ? ? ? ? ? BinaryExpression binary = expression as BinaryExpression;

? ? ? ? ? ? Console.WriteLine("運算符:" + expression.NodeType.ToString());

? ? ? ? ? ? VisitExpression2(binary.Left, ref ?lambdaOut);

? ? ? ? ? ? VisitExpression2(binary.Right, ref ?lambdaOut);

? ? ? ? ? ? break;

? ? ? ? case ExpressionType.Constant://常量值

? ? ? ? ? ? ConstantExpression constant = expression as ConstantExpression;

? ? ? ? ? ? Console.WriteLine("常量值:" + constant.Value.ToString());

? ? ? ? ? ? break;

? ? ? ? case ExpressionType.MemberAccess:

? ? ? ? ? ? MemberExpression Member = expression as MemberExpression;

? ? ? ? ? ? Console.WriteLine("字段名稱:{0},類型:{1}", Member.Member.Name, Member.Type.ToString());

? ? ? ? ? ? break;

? ? ? ? case ExpressionType.Quote:

? ? ? ? ? ? UnaryExpression Unary = expression as UnaryExpression;

? ? ? ? ? ? VisitExpression2(Unary.Operand, ref ?lambdaOut);

? ? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? ? Console.Write("UnKnow");

? ? ? ? ? ? break;

? ? }

}

然后重新實現方法?Execute?

public TResult Execute<TResult>(Expression expression)

{

? ? List<LambdaExpression> lambda = null;

? ? AnalysisExpression.VisitExpression2(expression, ref lambda);//解析取得表達式數中的表達式

? ? IEnumerable<Student> enumerable = null;

? ? for (int i = 0; i < lambda.Count; i++)

? ? {

? ? ? ? //把LambdaExpression轉成Expression<Func<Student, bool>>類型

? ? ? ? //通過方法Compile()轉成委托方法

? ? ? ? Func<Student, bool> func = (lambda[i] as Expression<Func<Student, bool>>).Compile();?

? ? ? ? if (enumerable == null)

? ? ? ? ? ? enumerable = Program.StudentArrary.Where(func);//取得IEnumerable

? ? ? ? else

? ? ? ? ? ? enumerable = enumerable.Where(func);

? ? }

? ? dynamic obj = enumerable.ToList();//(注意:這個方法的整個處理過程,你可以換成解析sql執行數據庫查詢,或者生成url然后請求獲取數據。)

? ? return (TResult)obj;

}

執行過程:

個人對?IQueryable?延遲加載的理解:

  • 前段部分的查詢操作符只是把邏輯分解存入表達式樹,并沒有遠程執行sql。

  • foreache執行的是?IEnumerable<T>?,然而?IEnumerable<T>?同樣具有延遲加載的特性。每次迭代的時候才真正的取數據。且在使用導航屬性的時候會再次查詢數據庫。(下次說延遲加載不要忘記了?IEnumerable?的功勞哦!)

小知識:

表達式樹轉成Lambda表達式:

Expression<Func<Student, bool>> expression = t => t.Name == "農碼一生"; Func<Student, bool> func = expression.Compile();

總結:

表達式樹的分析就告一段落了,其中還有很多細節或重要的沒有分析到。下次有新的心得再來總結。

感覺表達式樹就是先把表達式打散存在樹結構里(一般打散的過程是編譯器完成),然后可以根據不同的數據源或接口重新組裝成自己想要的任何形式,這也讓我們實現自己的orm成為了可能。

今天主要是對表達式樹的解析、和實現自己的IQueryable<T>、IQueryProvider做了一個記錄和總結,其中不定有錯誤的結論或說法,輕點拍!

文章首鏈:http://www.cnblogs.com/zhaopei/p/5792623.html

demo下載:http://pan.baidu.com/s/1nvAksgL?

?

推薦閱讀:

http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html

http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html

http://www.cnblogs.com/jesse2013/p/expressiontree-Linq-to-cnblogs.html


原文地址:http://www.cnblogs.com/zhaopei/p/IQueryable-IQueryProvider.html


.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的再讲IQueryablelt;Tgt;,揭开表达式树的神秘面纱的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。