C# Entity Framework中的IQueryable和IQueryProvider詳解
前言
相信大家對(duì)Entity Framework一定不陌生,我相信其中Linq To Sql是其最大的亮點(diǎn)之一,但是我們一直使用到現(xiàn)在卻不曾明白內(nèi)部是如何實(shí)現(xiàn)的,今天我們就簡(jiǎn)單的介紹IQueryable和IQueryProvider。
IQueryable接口
我們先聊聊這個(gè)接口,因?yàn)槲覀冊(cè)谑褂肊F中經(jīng)??吹絣inq to sql語句的返回類型是IQueryable,我們可以看下這個(gè)接口的結(jié)構(gòu):
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
或許會(huì)有人很奇怪,當(dāng)我們?cè)陂_發(fā)過程中使用這個(gè)接口的時(shí)候,提供的方法遠(yuǎn)遠(yuǎn)不止這么點(diǎn),因?yàn)槲④浱峁┝藦?qiáng)大的Queryable類,當(dāng)然大家不要以為這個(gè)類是實(shí)現(xiàn)IQueryable然后實(shí)現(xiàn)了很多方法,如果是那樣那些第三方庫怎么自定義呢?所以Queryable只是一個(gè)靜態(tài)類,對(duì)IQueryable接口進(jìn)行了擴(kuò)展,下面是筆者在.Net Reflector截圖中一部分:
如果讀者細(xì)心一點(diǎn)會(huì)發(fā)現(xiàn)linq to sql并不會(huì)導(dǎo)致實(shí)際的查詢,只有當(dāng)我們真正開始使用的時(shí)候才從數(shù)據(jù)庫中開始查詢數(shù)據(jù)。
IQueryProvider接口
如果我們調(diào)試的EF的話,會(huì)看到生成的T-SQL語句。T-SQL就是根據(jù)表達(dá)式樹分析從而得出的,而核心就是IQueryProvider接口,下面就是該接口的結(jié)構(gòu):
public interface IQueryProvider
{
IQueryable CreateQuery(Expression expression);
IQueryable<TElement> CreateQuery<TElement>(Expression expression);
object Execute(Expression expression);
TResult Execute<TResult>(Expression expression);
}
其中CreateQuery就是負(fù)責(zé)解析表達(dá)式樹的,當(dāng)然還要將處理后的結(jié)果返回,以便接著分析下面的語句,當(dāng)然這中間只是分析,你完全可以根據(jù)表達(dá)式樹得出你自己需要的查詢語句,比如SQL或者其他什么,只有在真正使用數(shù)據(jù)的時(shí)候才會(huì)調(diào)用Execute方法,這個(gè)時(shí)候就可以根據(jù)我們自己分析的語句開始進(jìn)行實(shí)際的查詢了。
實(shí)例分析
QueryProvider類
光說不練我們永遠(yuǎn)不能明白其中的原理,所以下面我們就簡(jiǎn)單的舉一個(gè)例子來展示下。首先我們先實(shí)現(xiàn)IQueryProvider接口,其中會(huì)用到一個(gè)Query類,這個(gè)類會(huì)在后面進(jìn)行介紹,首先我們新建一個(gè)QueryProvider類實(shí)現(xiàn)IQueryProvider接口,首先我們看下CreateQuery<S>方法:
這里的expression就是傳遞給我們,并且需要我們處理的表達(dá)式樹,最后還要返回實(shí)現(xiàn)IQueryable<S>接口的示例,以便LINQ在此基礎(chǔ)上進(jìn)行下面的查詢,這里我們僅僅只是創(chuàng)建了一個(gè)Query的實(shí)例,同時(shí)將expression傳遞給它,因?yàn)榇颂巸H僅只是一個(gè)DEMO,所以我們沒有去真正解析表達(dá)式樹(這其中要做的工作很多)。接著還有CreateQuery方法:
我們可以看到下面這句話:
實(shí)際的含義就是創(chuàng)建Query<>的實(shí)例,并且泛型參數(shù)是elementType,參數(shù)是this和expression。
最后就是Execute方法了,傳遞一個(gè)Expression參數(shù),并獲取最后的結(jié)果,筆者在這里直接是寫死的值:
Query類
僅僅只有QueryProvider還沒用,我們還需要一個(gè)能夠保存表達(dá)式樹狀態(tài)的類,當(dāng)然也包括了我們解析表達(dá)式后的結(jié)果也可以保存在其中,這樣我們?cè)贗QueryProvider的Execute方法中就可以根據(jù)我們解析的結(jié)果執(zhí)行執(zhí)行并返回結(jié)果了。
這里我們可以看到Query的Expression值在創(chuàng)建這個(gè)實(shí)例時(shí),如果沒有傳遞Expression參數(shù)時(shí)該值就是:
但是在后面的過程中Query中的Expression將是QueryProvider中的expression值。
到此我們其實(shí)就完成了一個(gè)簡(jiǎn)單的示例了,我們就可以開始測(cè)試我們的成果了,筆者在利用如下的代碼來測(cè)試:
OK,我們開始看看是如何分析這句LINQ語句的。
首先我們看下在一開始執(zhí)行時(shí)Query中Expression的返回值(如下圖):
在獲取到這個(gè)表達(dá)式后,就開始執(zhí)行Linq,首先執(zhí)行的是where item == 123。
分析Where item == 123
接著我們F5,就可以看到在QueryProvider中的CreateQuery<S>命中了,并且Expression參數(shù)如下圖所示:
我們看到里面的字符串是 Where(item => (item == 123)),通過這句話我們就可以明白其實(shí)LINQ中的where實(shí)質(zhì)上就是利用Where方法,并傳遞給它對(duì)應(yīng)的lambda表達(dá)式。分析完了where部分,下面就是FirstOrDefault部分了。
分析FirstOrDefault
當(dāng)執(zhí)行到FirstOrDefault的時(shí)候我們可以查看t的值,會(huì)發(fā)現(xiàn)t實(shí)際上就是QueryProvider中CreateQuery<S>的返回值。
接著我們開始執(zhí)行下面FirstOrDefault方法,發(fā)現(xiàn)會(huì)再一次的去獲取Expression的值,而此時(shí)Expression的值就是上面CreateQuery<T>傳遞給我們的參數(shù)expression。
然后在將這個(gè)表達(dá)式樹和由表達(dá)式樹表示FirstOrDefault方法調(diào)用的值拼接起來,并調(diào)用QueryProvider中的Execute<S>方法,我們可以看到這個(gè)時(shí)候傳遞給我們的參數(shù)expression的值。
至此一個(gè)簡(jiǎn)單的流程就結(jié)束了,最后就是返回筆者寫死的123這個(gè)值了。
通過上面這個(gè)例子我們基本了解了其工作的流程,下面我們將一步一步的分析我們這個(gè)where item == 123,當(dāng)然我們將會(huì)用到遞歸,所以請(qǐng)大家整理好自己的思路,一步一步的看如何從一個(gè)表達(dá)式樹中分析這條語句。
分析表達(dá)式樹實(shí)戰(zhàn)
首先我們一個(gè)分析表達(dá)式樹的方法,這個(gè)方法我們暫且放在QueryProvider中:
public void AnalysisExpression(Expression exp)
{
switch (exp.NodeType)
{
case ExpressionType.Call:
{
MethodCallExpression mce = exp as MethodCallExpression;
Console.WriteLine("The Method Is {0}", mce.Method.Name);
for (int i = 0; i < mce.Arguments.Count; i++)
{
AnalysisExpression(mce.Arguments[i]);
}
}
break;
case ExpressionType.Quote:
{
UnaryExpression ue = exp as UnaryExpression;
AnalysisExpression(ue.Operand);
}
break;
case ExpressionType.Lambda:
{
LambdaExpression le = exp as LambdaExpression;
AnalysisExpression(le.Body);
}
break;
case ExpressionType.Equal:
{
BinaryExpression be = exp as BinaryExpression;
Console.WriteLine("The Method Is {0}", exp.NodeType.ToString());
AnalysisExpression(be.Left);
AnalysisExpression(be.Right);
}
break;
case ExpressionType.Constant:
{
ConstantExpression ce = exp as ConstantExpression;
Console.WriteLine("The Value Type Is {0}", ce.Value.ToString());
}
break;
case ExpressionType.Parameter:
{
ParameterExpression pe = exp as ParameterExpression;
Console.WriteLine("The Parameter Is {0}", pe.Name);
}
break;
default:
{
Console.Write("UnKnow");
}
break;
}
}
并在CreateQuery<S>中調(diào)用這個(gè)方法
然后我們可以開始運(yùn)行測(cè)試了,為了能夠讓讀者明白當(dāng)前處理的表達(dá)式樹,所以在下面的截圖中將會(huì)包含AnalysisExpression中參數(shù)exp的值,這樣可以便于讀者區(qū)分當(dāng)前處理的表達(dá)式樹。
PS:Expression類型中的NodeType是非常重要的,因?yàn)閭鬟f給我們的都是父類Expression類型,而我們需要根據(jù)NodeType的轉(zhuǎn)換成對(duì)應(yīng)的子類,這樣我們才能夠獲取到更詳細(xì)的信息。
ExpressionType.Call
我們根據(jù)一開始的exp的NodeType進(jìn)入到這個(gè)分支,因?yàn)閣here實(shí)質(zhì)上就是ss調(diào)用where方法,所以我們通過將exp轉(zhuǎn)換成對(duì)應(yīng)的MethodCallExpression類型,這樣我們就可以看到調(diào)用的方法名稱了。
當(dāng)然調(diào)用一個(gè)方法必須要有參數(shù),所以下面還需要循環(huán)Arguments去分析具體的參數(shù),其中也包括調(diào)用這個(gè)方法的對(duì)象,自然我們首先是分析調(diào)用這個(gè)方法的對(duì)象,這里我們進(jìn)行了第一次的遞歸調(diào)用,跳到了ExpressionType.Constant。
ExpressionType.Constant
NodeType為這個(gè)類型,我們就可以通過ConstantExpression類型來獲取對(duì)應(yīng)的參數(shù),通過Value我們可以可以獲取到調(diào)用where方法的對(duì)象,當(dāng)然到這里就不會(huì)繼續(xù)往下分析了。
所以我們繼續(xù)跳到之前的for循環(huán),開始分析第二個(gè)參數(shù),就是 item => item == 123這個(gè)部分了。
ExpressionType.Quote
如果接觸過lambda的人可能會(huì)認(rèn)為類型應(yīng)該是Lambda,但實(shí)際上不會(huì)直接跳轉(zhuǎn)到那,而是先跳轉(zhuǎn)到Quote,然后我們?cè)侔艳D(zhuǎn)換成UnaryExpression類型,然后再繼續(xù)分析其中Operand屬性,而這個(gè)屬性的NodeType就是Lambda了。個(gè)人認(rèn)為這個(gè)應(yīng)該是區(qū)分lambda和普通的方法,因?yàn)閣here不僅僅可以接收lambda同時(shí)也可以是常規(guī)的方法,所以這里還需要這一層。
ExpressionType.Lambda
跳轉(zhuǎn)到這,大家就不會(huì)感覺奇怪了,這里為了簡(jiǎn)潔。筆者并沒有分析參數(shù),而是直接分析Body部分,因?yàn)檫@部分才是我們的關(guān)鍵。
ExpressionType.Equal
我們看到這個(gè)lambda很簡(jiǎn)單,就是一個(gè)相等比較,所以直接跳轉(zhuǎn)到了Equal,當(dāng)然還有And、Or等對(duì)應(yīng)的枚舉,而到了這一步我們就可以直接分析Left和Right,當(dāng)然這里還有一個(gè)小插曲,就是在跳到這個(gè)枚舉的時(shí)候我查看exp的類型時(shí),實(shí)際上是LogicalBinaryExpression類型,并不是BinaryExpression類型,然后用Reflector查看了下,我就呵呵了。
我當(dāng)時(shí)還奇怪,怎么沒有這個(gè)類型呢,最后才知道玩的是這一出。到此為止,我們繼續(xù)分析這個(gè)相等操作的左右兩邊的參數(shù)吧。
首先分析的是左邊參數(shù)item。
ExpressionType.Parameter
Item挑傳到這,并將其轉(zhuǎn)換成ParameterExpression類型,筆者在此僅僅只輸出了參數(shù)的名稱。
到這左邊的參數(shù)分析完畢,我們開始分析右邊的參數(shù)。
ExpressionType.Constant
我們可以輕松的想到對(duì)應(yīng)的Value就是123了,到此整個(gè)表達(dá)式就分析完畢了。
我們看看最后控制臺(tái)的輸出結(jié)果吧。
在此筆者還要聲明一個(gè)問題,就是我們應(yīng)該去理解我們使用的各種庫的原理,這樣便于我們以后添加符合實(shí)際開發(fā)的一些功能,當(dāng)然這并不是浪費(fèi)時(shí)間。而是提高今后項(xiàng)目開發(fā)的時(shí)間,隨著不斷的積累,我們會(huì)發(fā)現(xiàn)很多重復(fù)的功能并不需要我們?nèi)ブ貜?fù)寫了,而節(jié)省下來的時(shí)間我們就可以做自己想做的事了,所以我們要做一個(gè)有思想的懶程序員。
- C# IQueryable及IEnumerable區(qū)別解析
- C# Lambda表達(dá)式及Lambda表達(dá)式樹的創(chuàng)建過程
- C#用表達(dá)式樹構(gòu)建動(dòng)態(tài)查詢的方法
- C#表達(dá)式樹的基本用法講解
- C# 快速高效率復(fù)制對(duì)象(表達(dá)式樹)
- 淺談c#表達(dá)式樹Expression簡(jiǎn)單類型比較demo
- C# 表達(dá)式樹Expression Trees的知識(shí)梳理
- C#之Expression表達(dá)式樹實(shí)例
- c#反射表達(dá)式樹模糊搜索示例
- C# IQueryable<T>揭開表達(dá)式樹的神秘面紗
相關(guān)文章
C#使用BinaryFormatter類、ISerializable接口、XmlSerializer類進(jìn)行序列化和反序列
這篇文章介紹了C#使用BinaryFormatter類、ISerializable接口、XmlSerializer類進(jìn)行序列化和反序列化的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09Winform學(xué)生信息管理系統(tǒng)各子窗體剖析(3)
這篇文章主要針對(duì)Winform學(xué)生信息管理系統(tǒng)各子窗體進(jìn)行剖析,感興趣的小伙伴們可以參考一下2016-05-05C#關(guān)于Task.Yeild()函數(shù)的討論
這篇文章主要介紹了C#中關(guān)于Task.Yeild()函數(shù)的相關(guān)資料,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07C#實(shí)現(xiàn)鬧鐘AlarmClock實(shí)例代碼
這篇文章主要介紹了C#實(shí)現(xiàn)鬧鐘AlarmClock實(shí)例代碼,很實(shí)用的功能,需要的朋友可以參考下2014-08-08C# 中使用Stopwatch計(jì)時(shí)器實(shí)現(xiàn)暫停計(jì)時(shí)繼續(xù)計(jì)時(shí)功能
這篇文章主要介紹了C# 中使用Stopwatch計(jì)時(shí)器可暫停計(jì)時(shí)繼續(xù)計(jì)時(shí),主要介紹stopwatch的實(shí)例代碼詳解,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03c#中WebService的介紹及調(diào)用方式小結(jié)
這篇文章主要給大家介紹了關(guān)于c#中的WebService及其調(diào)用方式的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11基于c#實(shí)現(xiàn)的九九乘法表(簡(jiǎn)單實(shí)例)
本文主要分享了基于c#實(shí)現(xiàn)的九九乘法表,代碼簡(jiǎn)潔,需要的朋友可以參考下,希望對(duì)大家有所幫助2016-12-12