当前位置 > 首页 > Asp.net

.NET深入解析LINQ框架(三:LINQ优雅的前奏)

2012-12-4 13:24:00来源:Asp.net

5】.动态LINQ查询(动态构建Expression<T>表达式树)

什么是动态LINQ查询?LINQ的编写是静态的,因为C#是基于静态类型系统原理设计的,在编写时已经确定类型,也就是在编译时就已经知道将要执行什么样的查询,条件是什么、排序方式是什么等等。那么很大一部分应用场合中我们需要根据用户的选择来查询数据源,以往我们都是通过判断的方式来拼接查询的SQL字符串,但是现在我们面对是强类型的LINQ查询,是否可以很方便的进行类似查询。其实也没有什么好神秘的,基本的实现原理是通过动态的构建表达式树来实现IQueryable<T>接口的查询。

其实动态LINQ查询所能执行的最关键的因素在于Expression<T>对象是可以被动态编译成可以执行的委托对象,委托对象是完全可以被直接使用的可执行代码段,这就为动态LINQ查询提供了基础。对于IEnumerable<T>类型的查询表达式方法都知道它的执行是不会直接接受Expression<T>类型对象的,那么动态LINQ是否能工作于IEnumerable<T>接口?其实可以的,有个很隐蔽的窍门隐藏在IQueryable<T>扩展方法对象Queryable中,也就是AsQueryable<T>方法,它返回的是一个实现了IQueryable<T>接口的EnumerableQuery对象,该对象的实现内容不是很复杂,将动态拼接的数据结构Expression<T>对象编译成可以执行的匿名函数,然后直接执行查询。[王清培版权所有,转载请给出署名]

我们来看一下EnumerableQuery对象的重点,它肯定有一个地方是将Expression<T>对象Compiler的地方。

1 private IEnumerator<T> GetEnumerator()
2 {
3 if (this.enumerable == null)
4 {
5 EnumerableRewriter rewriter = new EnumerableRewriter();
6 Expression<Func<IEnumerable<T>>> expression2 =
7 Expression.Lambda<Func<IEnumerable<T>>>(rewriter.Visit(this.expression), (IEnumerable<ParameterExpression>)null);
8 this.enumerable = expression2.Compile()(); //(1)重点
9 }
10 return this.enumerable.GetEnumerator();
11 }

在上述代码中的“(1)重点”的地方,我们很清楚的看见表达式树被动态编译后然后紧接着又被执行,这里就能看出为什么IEnumerable<T>对象需要能够被转换成IQueryable<T>对象。这样就可以消除IEnumerable<T>、IQueryable<T>这两个接口之间的动态查询瓶颈。

为什么需要动态LINQ查询,上面说过问题出在我们没办法在运行时再去编写Lambda表达式了,都知道Lambda表达式到最后就是被编译成Expression表达式树对象,所以我们可以在运行时自己动态的构建Expression对象,这样就可以将动态构建出来的表达式树对象直接传入到需要的方法中。如果查询的数据对象是IEnumerable<T>则会被动态编译成可以执行的委托然后直接执行,如果查询的是IQueryable<T>则顺其自然的被提供程序解析执行。

下面我们来看一个简单的动态查询例子:

1 //构造Student数组
2 Student[] StudentArrary = new Student[3]
3 {
4 new Student(){Name="王清培", Age=24, Sex="", Address="江苏南京"},
5 new Student(){Name="陈玉和", Age=23, Sex="", Address="江苏盐城"},
6 new Student(){Name="金源", Age=22, Sex="", Address="江苏淮安"}
7 };

这是一组数据,为了简单测试就不搞那么麻烦的Linq to Sql数据源了。我们将要通过动态的构建表达式树来做为查询的逻辑,以往我们的Lambda在这个时候派不上用场了,在运行时我们无法再去构建委托类型。

现在的需求是从界面上接受一