IEnumerable与IQueryable形式的数据源之间的区别
IQueryable
尽管它们返回的结果相同,但其工作方式却大有不同。第一种写法采用的是IQueryable
等到用户想要遍历查询结果的时候,LINQ to SQL程序库会把相关的查询操作合起来执行,并给出查询结果。具体到本例来说,这意味着只需向数据库发出一次调用即可,而且where子句与orderby子句也能在同一次SQL查询操作里面完成。
第二种写法会把经过where子句所过滤的结果转成IEnumerable
你需要注意这种区别,因为有些功能用IQueryable实现起来要比用IEnumerable快得多。此外,你还应该了解IQueryable与IEnumerable会分别采用怎样的方式来处理查询表达式,因为某些写法只适用于其中一种环境,而不能在另一种环境下正常运作。
由于两者会用不同的类型来表示处理过程中所涉及的数据,因此,它们所依循的其实是两套完全不同的流程。无论是用lambda表达式来撰写查询逻辑还是以函数参数的形式来表示这些逻辑,针对IEnumerable
用IQueryable实现出来的版本则会解析表达式树,在解析的时候,系统会把这棵树所表示的逻辑转换成provider能够操作的格式,并将其放在离数据最近的地方去执行。这意味着需要传输的数据会远远少于IEnumerable版本,而且总体性能也更好。然而,在IQueryable型的序列上面查询是会受到一些限制的,因为并不是所有的操作都可以出现在查询表达式中,只有那些为底层实现所支持的操作才可以写入表达式。
本章早前的第37条说过,用来支持IQueryable的那些provider未必能够解析每一种查询方法,因为假如要保证每个方法都能得到解析,那就得把用户有可能写出的每一种逻辑全都考虑进来。实际上,那些provider只能解读某几种固定的运算符,而且有可能只支持某一套固定的方法,那些方法是.NET Framework已经实现好的。如果你要在查询操作里面调用除此之外的其他方法,那么可能得把序列当成IEnumerable来查询(而不能直接以IQueryable的形式查询)。
第一种写法能够正常运作,因为LINQ to Objects会以委托的形式将这些查询操作实现成方法调用。用了AsEnumerable()方法之后,查询工作就必须在本地用户所处的环境中执行,而且where子句内的逻辑也要由LINQ to Objects来处理。第二种写法会抛出异常,因为LINQ to SQL是用IQueryable
如果在性能与健壮这两项因素之间更看重后者,那么可以把查询结果明确转换成IEnumerable
这听上去还不错,而且写起来也很简单,但这种写法会迫使程序一律以IEnumerable
一般情况下,应该把那些分别针对各个类型但又极为相似的逻辑纳入同一个方法中,该方法只需针对那个能与其他类型相兼容且最为具体的类或接口来编写即可。然而涉及IEnumerable
有时,程序可能需要针对某种数据类型T来同时支持IEnumerable
这两个方法之间有很多代码都是重复的。为此,开发者可以用AsQueryable()把IEnumerable
AsQueryable()会判断序列的运行期类型。如果是IQueryable型,那就把该序列当成IQueryable返回。若是IEnumerable型,则会用LINQ to Objects的逻辑来创建一个实现IQueryable的wrapper(包装器),然后将其返回。尽管这个wrapper在其内部用的还是IEnumerable形式的处理逻辑,但它却是以IQueryable形式的引用而出现的。
使用AsQueryable()来编写代码可以同时顾及这两种情况,也就是说,无论序列是本身就已经实现了IQueryable,还是仅仅实现了IEnumerable,都能够充当数据源。如果是前一种情况,那么代码就可以适当地运用IQueryable
需要注意的是,现在的这个版本还是调用了一个方法,也就是string.LastIndex Of()方法,只不过,该方法恰好可以为LINQ to SQL库所解析,因此,能够放在LINQ to SQL查询中。然而由于各provider的能力不尽相同,因此,无法确保IQuery Provider的每一种实现方式都能够支持该方法。
IQueryable
来着《Effective C#:改善C#代码的50个有效方法(原书第3版)》