LINQ 表達(dá)式樹(shù)

您已在上一節(jié)中了解了表達(dá)式。現(xiàn)在,讓我們?cè)谶@里了解表達(dá)式樹(shù)。

顧名思義,表達(dá)式樹(shù)不過(guò)是按樹(shù)狀數(shù)據(jù)結(jié)構(gòu)排列的表達(dá)式。表達(dá)式樹(shù)中的每個(gè)節(jié)點(diǎn)都是一個(gè)表達(dá)式。例如,表達(dá)式樹(shù)可用于表示數(shù)學(xué)公式x <y,其中x,<和y將表示為表達(dá)式,并排列在樹(shù)狀結(jié)構(gòu)中。

表達(dá)式樹(shù)是lambda表達(dá)式的內(nèi)存表示形式。它保存查詢的實(shí)際元素,而不是查詢的結(jié)果。

表達(dá)式樹(shù)使lambda表達(dá)式的結(jié)構(gòu)透明和顯式。您可以與表達(dá)式樹(shù)中的數(shù)據(jù)進(jìn)行交互,就像與其他任何數(shù)據(jù)結(jié)構(gòu)一樣。

例如,看以下isTeenAgerExpr表達(dá)式:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

編譯器會(huì)將上面的表達(dá)式轉(zhuǎn)換為以下表達(dá)式樹(shù):

示例:C#中的表達(dá)式樹(shù)

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

您也可以手動(dòng)構(gòu)建表達(dá)式樹(shù)。讓我們看看如何為以下簡(jiǎn)單的lambda表達(dá)式構(gòu)建表達(dá)式樹(shù):

示例:C#中的Func委托:

Func<Student, bool> isAdult = s => s.age >= 18;

此Func類(lèi)型委托將被視為以下方法:

 C#:

public bool function(Student s)
{
  return s.Age > 18;
}

要?jiǎng)?chuàng)建表達(dá)式樹(shù),首先,創(chuàng)建一個(gè)參數(shù)表達(dá)式,其中Student是參數(shù)的類(lèi)型,'s'是參數(shù)的名稱(chēng),如下所示:

步驟1:在C#中創(chuàng)建參數(shù)表達(dá)式

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

現(xiàn)在,使用Expression.Property()創(chuàng)建s.Age表達(dá)式,其中s是參數(shù),Age是Student的屬性名稱(chēng)。(Expression是一個(gè)抽象類(lèi),其中包含用于手動(dòng)創(chuàng)建表達(dá)式樹(shù)的靜態(tài)幫助器方法。)

步驟2:在C#中創(chuàng)建屬性表達(dá)式

MemberExpression me = Expression.Property(pe, "Age");

現(xiàn)在,為18創(chuàng)建一個(gè)常量表達(dá)式:

步驟3:在C#中創(chuàng)建常量表達(dá)式

ConstantExpression constant = Expression.Constant(18, typeof(int));

到目前為止,我們已經(jīng)為s.Age(成員表達(dá)式)和18(常量表達(dá)式)構(gòu)建了表達(dá)式樹(shù)?,F(xiàn)在,我們需要檢查成員表達(dá)式是否大于常量表達(dá)式。為此,請(qǐng)使用Expression.GreaterThanOrEqual() 方法,并將成員表達(dá)式和常量表達(dá)式作為參數(shù)傳遞::

步驟4:在C#中創(chuàng)建二進(jìn)制表達(dá)式

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

因此,我們?yōu)閘ambda表達(dá)式主體 s.Age> = 18 構(gòu)建了一個(gè)表達(dá)式樹(shù)。我們現(xiàn)在需要將參數(shù)表達(dá)式和主體表達(dá)式連接起來(lái)。使用Expression.Lambda(body,parameters array)連接lambda表達(dá)式s => s.age> = 18的body(主體)和parameter(參數(shù))部分:

步驟5:在C#中創(chuàng)建Lambda表達(dá)式

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

這樣,您可以為帶有l(wèi)ambda表達(dá)式的簡(jiǎn)單Func委托構(gòu)建表達(dá)式樹(shù)。

示例:C#中的表達(dá)式樹(shù)

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

MemberExpression me = Expression.Property(pe, "Age");

ConstantExpression constant = Expression.Constant(18, typeof(int));

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

Console.WriteLine("表達(dá)式樹(shù): {0}", ExpressionTree);
        
Console.WriteLine("表達(dá)式樹(shù)體: {0}", ExpressionTree.Body);
        
Console.WriteLine("表達(dá)式樹(shù)中的參數(shù)個(gè)數(shù): {0}", 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("表達(dá)式樹(shù)中的參數(shù): {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")

Dim mexp As MemberExpression = Expression.Property(pe, "Age")

Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))

Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)

Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() {pe})

Console.WriteLine("表達(dá)式樹(shù): {0}", ExpressionTree)

Console.WriteLine("表達(dá)式樹(shù)體: {0}", ExpressionTree.Body)
        
Console.WriteLine("表達(dá)式樹(shù)中的參數(shù)個(gè)數(shù): {0}", 
                                ExpressionTree.Parameters.Count)
        
Console.WriteLine("表達(dá)式樹(shù)中的參數(shù): {0}", ExpressionTree.Parameters(0))
輸出:
表達(dá)式樹(shù): s => (s.Age >= 18)
表達(dá)式樹(shù)體: (s.Age >= 18)
表達(dá)式樹(shù)中的參數(shù)個(gè)數(shù): 1
表達(dá)式樹(shù)中的參數(shù): s

下圖說(shuō)明了創(chuàng)建表達(dá)式樹(shù)的整個(gè)過(guò)程:

linq構(gòu)造表達(dá)式樹(shù)。
構(gòu)造表達(dá)式樹(shù)

為什么選擇表達(dá)樹(shù)?

在上一節(jié)中,我們已經(jīng)看到分配給lambda表達(dá)式Func<T>編譯為可執(zhí)行代碼,分配給lambda表達(dá)式Expression<TDelegate>類(lèi)型編譯為Expression樹(shù)。

可執(zhí)行代碼在同一個(gè)應(yīng)用程序域中執(zhí)行,以處理內(nèi)存中的集合??擅杜e的靜態(tài)類(lèi)包含用于實(shí)現(xiàn)IEnumerable <T>接口的內(nèi)存中集合的擴(kuò)展方法,例如List <T>,Dictionary <T>等。Enumerable類(lèi)中的擴(kuò)展方法接受Func類(lèi)型委托的謂詞參數(shù)。例如,Where擴(kuò)展方法接受Func <TSource,bool>謂詞。然后,將其編譯為IL(中間語(yǔ)言)以處理同一AppDomain中的內(nèi)存中集合。

下圖顯示了Enumerable類(lèi)中的where擴(kuò)展方法包括Func委托作為參數(shù)的情況:

Where 的Func 委托

Func委托是原始的可執(zhí)行代碼,因此,如果調(diào)試代碼,則會(huì)發(fā)現(xiàn)Func委托將表示為不透明代碼。您無(wú)法看到其參數(shù),返回類(lèi)型和主體:

調(diào)試模式下的Func委托
調(diào)試模式下的Func委托

Func委托用于內(nèi)存中的集合,因?yàn)樗鼘⒃谕粋€(gè)AppDomain中進(jìn)行處理,但是諸如LINQ-to-SQL,EntityFramework或其他提供LINQ功能的第三方產(chǎn)品的遠(yuǎn)程LINQ查詢提供者呢?他們將如何解析已編譯為原始可執(zhí)行代碼的lambda表達(dá)式,以了解參數(shù),lambda表達(dá)式的返回類(lèi)型以及構(gòu)建運(yùn)行時(shí)查詢以進(jìn)一步處理?答案是表達(dá)樹(shù)。

Expression <TDelegate>被編譯成稱(chēng)為表達(dá)式樹(shù)的數(shù)據(jù)結(jié)構(gòu)。

如果調(diào)試代碼,則表達(dá)式代表將如下所示:

ExpressionTree處于調(diào)試模式
調(diào)試模式下的表達(dá)式樹(shù)

現(xiàn)在您可以看到普通委托和表達(dá)式之間的區(qū)別。表達(dá)式樹(shù)是透明的。您可以從表達(dá)式中檢索參數(shù),返回類(lèi)型和主體表達(dá)式信息,如下所示:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;

Console.WriteLine("Expression: {0}", isTeenAgerExpr );
        
Console.WriteLine("表達(dá)式類(lèi)型: {0}", isTeenAgerExpr.NodeType);

var parameters = isTeenAgerExpr.Parameters;

foreach (var param in parameters)
{
    Console.WriteLine("參數(shù)名稱(chēng): {0}", param.Name);
    Console.WriteLine("參數(shù)類(lèi)型: {0}", param.Type.Name );
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;

Console.WriteLine("表達(dá)式主體左側(cè): {0}", bodyExpr.Left);
Console.WriteLine("二進(jìn)制表達(dá)式類(lèi)型: {0}", bodyExpr.NodeType);
Console.WriteLine("表達(dá)式主體右側(cè): {0}", bodyExpr.Right);
Console.WriteLine("返回類(lèi)型: {0}", isTeenAgerExpr.ReturnType);
輸出:
Expression: s => ((s.Age > 12) AndAlso (s.Age < 20))
表達(dá)式類(lèi)型: Lambda
參數(shù)名稱(chēng): s
參數(shù)類(lèi)型: Student
表達(dá)式主體左側(cè): (s.Age > 12)
二進(jìn)制表達(dá)式類(lèi)型: AndAlso
表達(dá)式主體右側(cè): (s.Age < 20)
返回類(lèi)型: System.Boolean

不在同一應(yīng)用程序域中執(zhí)行針對(duì)LINQ-to-SQL或Entity Framework的LINQ查詢。例如,對(duì)于Entity Framework的以下LINQ查詢永遠(yuǎn)不會(huì)在程序內(nèi)部實(shí)際執(zhí)行:

示例:C#中的LINQ查詢
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

首先將其轉(zhuǎn)換為SQL語(yǔ)句,然后在數(shù)據(jù)庫(kù)服務(wù)器上執(zhí)行。

在查詢表達(dá)式中找到的代碼必須轉(zhuǎn)換為SQL查詢,該查詢可以作為字符串發(fā)送到另一個(gè)進(jìn)程。對(duì)于LINQ-to-SQL或Entity Framework,該過(guò)程恰好是SQL Server數(shù)據(jù)庫(kù)。將數(shù)據(jù)結(jié)構(gòu)(如表達(dá)式樹(shù))轉(zhuǎn)換為SQL顯然比將原始IL或可執(zhí)行代碼轉(zhuǎn)換為SQL容易得多,因?yàn)檎缒吹降?,從表達(dá)式中檢索信息很容易。

創(chuàng)建表達(dá)式樹(shù)的目的是將諸如查詢表達(dá)式之類(lèi)的代碼轉(zhuǎn)換為可以傳遞給其他進(jìn)程并在此處執(zhí)行的字符串。

可查詢的靜態(tài)類(lèi)包括接受Expression類(lèi)型的謂詞參數(shù)的擴(kuò)展方法。將該謂詞表達(dá)式轉(zhuǎn)換為表達(dá)式樹(shù),然后將其作為數(shù)據(jù)結(jié)構(gòu)傳遞到遠(yuǎn)程LINQ提供程序,以便提供程序可以從表達(dá)式樹(shù)構(gòu)建適當(dāng)?shù)牟樵儾?zhí)行查詢。

丰满人妻一级特黄a大片,午夜无码免费福利一级,欧美亚洲精品在线,国产婷婷成人久久Av免费高清