使用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