首页 > 代码库 > 使用Expression Tree构建动态LINQ查询

使用Expression Tree构建动态LINQ查询

这篇文章介绍一个有意思的话题,也是经常被人问到的:如何构建动态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

?