這篇文章介紹一個有意思的話題,也是經常被人問到的:如何構建動態LINQ查詢?所謂動態,主要的意思在於查詢的條件可以隨機組合,動態添加,而不是固定的寫法。這個在很多系統開發過程中是非常有用的。
我這里給的一個解決方案是采用Expression Tree來構建。
其實這個技術很早就有,在.NET Framework 3.5開始引入。之前也有不少同學寫過很多不錯的理論性文章。我自己當年學習這個,覺得最好的幾篇文章是由"裝配腦袋"同學寫的。【有時間請仔細閱讀這些入門指南,做點練習基本就能理解】
Expression Tree上手指南 (一) - 裝配腦袋 - 博客園
Expression Tree 上手指南 (二) - 裝配腦袋 - 博客園
Expression Tree 上手指南 (三) - 裝配腦袋 - 博客園
我下面給出的這個實例,希望能幫助大家更加深入理解這個技術,並且結合常見的LINQ to SQL來實現動態的查詢。
下面這個查詢,大家應該都很眼熟
如果我們的條件是固定的,例如上例中,一共有兩個條件,而且條件的邏輯判斷也都是確定的,那么上面這樣寫很容易就能得到我們的結果。
但,問題是,如果我們的條件不是固定的呢?如果你需要根據用戶的選擇,然后動態構造一個查詢呢?
我看過很多人做的一些通用查詢界面,為了應對用戶希望自主選擇條件的這個需求,他們的做法往往就是用"拼接查詢字符串"的做法來實現。這種方法勉強能實現要求,但性能和可維護性方面都相當差。
如果你了解了Expression Tree,那么上面這個查詢可以修改為下面這樣:
由此可見,掌握了這個技術的話,那么以后寫動態查詢應該會如虎添翼,至少多了一種很好的思路。
順便說一下,這個技術和反射有點類似,屬於比較底層的技術,掌握了將對大家的編程能力會有所提升。
值得一說的是,就算是我們第一種寫法,內部的實現也是使用Expression Tree來實現的,有興趣的同學可以看看如下的IL代碼。
IL_0001: ldarg.0
IL_0002: call LINQPad.User.TypedDataContext.get_Employees
IL_0007: ldtoken LINQPad.User.Employees
IL_000C: call System.Type.GetTypeFromHandle
IL_0011: ldstr "x"
IL_0016: call System.Linq.Expressions.Expression.Parameter
IL_001B: stloc.1 // CS$0$0000
IL_001C: ldloc.1 // CS$0$0000
IL_001D: ldtoken LINQPad.User.Employees.EmployeeID
IL_0022: call System.Reflection.FieldInfo.GetFieldFromHandle
IL_0027: call System.Linq.Expressions.Expression.Field
IL_002C: ldc.i4.5
IL_002D: box System.Int32
IL_0032: ldtoken System.Int32
IL_0037: call System.Type.GetTypeFromHandle
IL_003C: call System.Linq.Expressions.Expression.Constant
IL_0041: call System.Linq.Expressions.Expression.GreaterThan
IL_0046: ldloc.1 // CS$0$0000
IL_0047: ldtoken LINQPad.User.Employees.Title
IL_004C: call System.Reflection.FieldInfo.GetFieldFromHandle
IL_0051: call System.Linq.Expressions.Expression.Field
IL_0056: ldstr "Sales Representative"
IL_005B: ldtoken System.String
IL_0060: call System.Type.GetTypeFromHandle
IL_0065: call System.Linq.Expressions.Expression.Constant
IL_006A: ldc.i4.0
IL_006B: ldtoken System.String.op_Equality
IL_0070: call System.Reflection.MethodBase.GetMethodFromHandle
IL_0075: castclass System.Reflection.MethodInfo
IL_007A: call System.Linq.Expressions.Expression.Equal
IL_007F: call System.Linq.Expressions.Expression.AndAlso
IL_0084: ldc.i4.1
IL_0085: newarr System.Linq.Expressions.ParameterExpression
IL_008A: stloc.2 // CS$0$0001
IL_008B: ldloc.2 // CS$0$0001
IL_008C: ldc.i4.0
IL_008D: ldloc.1 // CS$0$0000
IL_008E: stelem.ref
IL_008F: ldloc.2 // CS$0$0001
IL_0090: call System.Linq.Expressions.Expression.Lambda
IL_0095: call System.Linq.Queryable.Where
IL_009A: stloc.0 // query
IL_009B: ldloc.0 // query
IL_009C: call LINQPad.Extensions.Dump