1. 程式人生 > >C# Entity Framework中的IQueryable和IQueryProvider詳解

C# Entity Framework中的IQueryable和IQueryProvider詳解

oid display provide 分析 當前 負責 nbsp enum share


前言

相信大家對

Entity Framework

一定不陌生,我相信其中Linq To Sql是其最大的亮點之一,但是我們一直使用到現在卻不曾明白內部是如何實現的,今天我們就簡單的介紹IQueryable和IQueryProvider。


IQueryable接口

我們先聊聊這個接口,因為我們在使用EF中經常看到linq to sql語句的返回類型是

IQueryable

,我們可以看下這個接口的結構:


代碼如下:


public interface IQueryable : IEnumerable

{

Type ElementType { get; }

Expression Expression { get; }

IQueryProvider Provider { get; }

}

或許會有人很奇怪,當我們在開發過程中使用這個接口的時候,提供的方法遠遠不止這麽點,因為微軟提供了強大的

Queryable

類,當然大家不要以為這個類是實現IQueryable然後實現了很多方法,如果是那樣那些第三方庫怎麽自定義呢?所以Queryable只是一個靜態類,對IQueryable接口進行了擴展,下面是筆者在


.Net Reflector


截圖中一部分:

技術分享圖片

如果讀者細心一點會發現linq to sql並不會導致實際的查詢,只有當我們真正開始使用的時候才從數據庫中開始查詢數據。


IQueryProvider接口

如果我們調試的EF的話,會看到生成的T-SQL語句。T-SQL就是根據表達式樹分析從而得出的,而核心就是IQueryProvider接口,下面就是該接口的結構:


代碼如下:


public interface IQueryProvider

{

IQueryable CreateQuery(Expression expression);

IQueryable<TElement> CreateQuery<TElement>(Expression expression);

object Execute(Expression expression);

TResult Execute<TResult>(Expression expression);

}

其中

CreateQuery

就是負責解析表達式樹的,當然還要將處理後的結果返回,以便接著分析下面的語句,當然這中間只是分析,你完全可以根據表達式樹得出你自己需要的查詢語句,比如SQL或者其他什麽,只有在真正使用數據的時候才會調用

Execute


方法,這個時候就可以根據我們自己分析的語句開始進行實際的查詢了。


實例分析


QueryProvider類

光說不練我們永遠不能明白其中的原理,所以下面我們就簡單的舉一個例子來展示下。首先我們先實現IQueryProvider接口,其中會用到一個Query類,這個類會在後面進行介紹,首先我們新建一個

QueryProvider

類實現IQueryProvider接口,首先我們看下

CreateQuery<S>

方法:

技術分享圖片

這裏的

expression

就是傳遞給我們,並且需要我們處理的表達式樹,最後還要返回實現IQueryable<S>接口的示例,以便LINQ在此基礎上進行下面的查詢,這裏我們僅僅只是創建了一個Query的實例,同時將expression傳遞給它,因為此處僅僅只是一個DEMO,所以我們沒有去真正解析表達式樹

(這其中要做的工作很多)

。接著還有CreateQuery方法:

技術分享圖片

我們可以看到下面這句話:

技術分享圖片

實際的含義就是創建

Query<>

的實例,並且泛型參數是

elementType

,參數是

this



expression

最後就是

Execute

方法了,傳遞一個

Expression

參數,並獲取最後的結果,筆者在這裏直接是寫死的值:

技術分享圖片


Query類

僅僅只有

QueryProvider

還沒用,我們還需要一個能夠保存表達式樹狀態的類,當然也包括了我們解析表達式後的結果也可以保存在其中,這樣我們在IQueryProvider的Execute方法中就可以根據我們解析的結果執行執行並返回結果了。

技術分享圖片

這裏我們可以看到Query的Expression值在創建這個實例時,如果沒有傳遞Expression參數時該值就是:

技術分享圖片

但是在後面的過程中Query中的Expression將是QueryProvider中的expression值。

技術分享圖片

到此我們其實就完成了一個簡單的示例了,我們就可以開始測試我們的成果了,筆者在利用如下的代碼來測試:

技術分享圖片

OK,我們開始看看是如何分析這句LINQ語句的。

首先我們看下在一開始執行時Query中Expression的返回值(如下圖):

技術分享圖片

在獲取到這個表達式後,就開始執行Linq,首先執行的是where item == 123。


分析Where item == 123

接著我們F5,就可以看到在QueryProvider中的

CreateQuery<S>

命中了,並且

Expression

參數如下圖所示:

技術分享圖片

我們看到裏面的字符串是


Where(item => (item == 123))


,通過這句話我們就可以明白其實LINQ中的where實質上就是利用Where方法,並傳遞給它對應的lambda表達式。分析完了where部分,下面就是

FirstOrDefault

部分了。


分析FirstOrDefault

當執行到

FirstOrDefault

的時候我們可以查看t的值,會發現t實際上就是QueryProvider中CreateQuery<S>的返回值。

技術分享圖片

接著我們開始執行下面

FirstOrDefault

方法,發現會再一次的去獲取

Expression

的值,而此時Expression的值就是上面

CreateQuery<T>

傳遞給我們的參數

expression

技術分享圖片

然後在將這個表達式樹和由表達式樹表示

FirstOrDefault

方法調用的值拼接起來,並調用QueryProvider中的Execute<S>方法,我們可以看到這個時候傳遞給我們的參數expression的值。

技術分享圖片

至此一個簡單的流程就結束了,最後就是返回筆者寫死的123這個值了。

技術分享圖片

通過上面這個例子我們基本了解了其工作的流程,下面我們將一步一步的分析我們這個

where item == 123

,當然我們將會用到遞歸,所以請大家整理好自己的思路,一步一步的看如何從一個表達式樹中分析這條語句。


分析表達式樹實戰

首先我們一個分析表達式樹的方法,這個方法我們暫且放在

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>


中調用這個方法

技術分享圖片

然後我們可以開始運行測試了,為了能夠讓讀者明白當前處理的表達式樹,所以在下面的截圖中將會包含


AnalysisExpression中參數exp


的值,這樣可以便於讀者區分當前處理的表達式樹。

PS:Expression類型中的NodeType是非常重要的,因為傳遞給我們的都是父類Expression類型,而我們需要根據NodeType的轉換成對應的子類,這樣我們才能夠獲取到更詳細的信息。


ExpressionType.Call

我們根據一開始的exp的NodeType進入到這個分支,因為where實質上就是ss調用where方法,所以我們通過將exp轉換成對應的

MethodCallExpression

類型,這樣我們就可以看到調用的方法名稱了。

技術分享圖片

當然調用一個方法必須要有參數,所以下面還需要循環

Arguments

去分析具體的參數,其中也包括調用這個方法的對象,自然我們首先是分析調用這個方法的對象,這裏我們進行了第一次的遞歸調用,跳到了ExpressionType.Constant。


ExpressionType.Constant

NodeType為這個類型,我們就可以通過

ConstantExpression

類型來獲取對應的參數,通過Value我們可以可以獲取到調用where方法的對象,當然到這裏就不會繼續往下分析了。

技術分享圖片

所以我們繼續跳到之前的for循環,開始分析第二個參數,就是 item => item == 123這個部分了。

技術分享圖片


ExpressionType.Quote

如果接觸過lambda的人可能會認為類型應該是Lambda,但實際上不會直接跳轉到那,而是先跳轉到Quote,然後我們再把轉換成

UnaryExpression

類型,然後再繼續分析其中


Operand


屬性,而這個屬性的NodeType就是Lambda了。個人認為這個應該是區分lambda和普通的方法,因為where不僅僅可以接收lambda同時也可以是常規的方法,所以這裏還需要這一層。

技術分享圖片


ExpressionType.Lambda

跳轉到這,大家就不會感覺奇怪了,這裏為了簡潔。筆者並沒有分析參數,而是直接分析

Body

部分,因為這部分才是我們的關鍵。

技術分享圖片


ExpressionType.Equal

我們看到這個lambda很簡單,就是一個相等比較,所以直接跳轉到了Equal,當然還有And、Or等對應的枚舉,而到了這一步我們就可以直接分析Left和Right,當然這裏還有一個小插曲,就是在跳到這個枚舉的時候我查看exp的類型時,實際上是

LogicalBinaryExpression

類型,並不是

BinaryExpression

類型,然後用Reflector查看了下,我就呵呵了。

技術分享圖片

我當時還奇怪,怎麽沒有這個類型呢,最後才知道玩的是這一出。到此為止,我們繼續分析這個相等操作的左右兩邊的參數吧。

技術分享圖片

首先分析的是左邊參數item。


ExpressionType.Parameter

Item挑傳到這,並將其轉換成

ParameterExpression

類型,筆者在此僅僅只輸出了參數的名稱。

技術分享圖片

到這左邊的參數分析完畢,我們開始分析右邊的參數。


ExpressionType.Constant

我們可以輕松的想到對應的Value就是123了,到此整個表達式就分析完畢了。

技術分享圖片

我們看看最後控制臺的輸出結果吧。

技術分享圖片

在此筆者還要聲明一個問題,就是我們應該去理解我們使用的各種庫的原理,這樣便於我們以後添加符合實際開發的一些功能,當然這並不是浪費時間。而是提高今後項目開發的時間,隨著不斷的積累,我們會發現很多重復的功能並不需要我們去重復寫了,而節省下來的時間我們就可以做自己想做的事了,所以我們要做一個有思想的懶程序員。

除聲明外,跑步客文章均為原創,轉載請以鏈接形式標明本文地址
C# Entity Framework中的IQueryable和IQueryProvider詳解

本文地址: http://www.paobuke.com/develop/c-develop/pbk23160.html






相關內容

技術分享圖片C#中控件動態添加事件綁定的時機詳解技術分享圖片C#七大經典排序算法系列(上)技術分享圖片C#語法新特性之元組實例詳解技術分享圖片C#中如何利用正則表達式判斷字符
技術分享圖片舉例講解C#編程中對設計模式中的單例模式的運用技術分享圖片超炫酷的WPF實現Loading控件效果技術分享圖片C#實現windows form拷貝內容到剪貼板的方法技術分享圖片C#畫圓角矩形的方法

C# Entity Framework中的IQueryable和IQueryProvider詳解