阅读目录:
- 1.开篇介绍
-
2.扩展Linq to Object (应用框架具有查询功能)
-
2.1.通过添加IEnumerable
对象的扩展方法 -
2.2.通过继承IEnumerable
接口 -
2.3.详细的对象结构图
-
-
3.实现IQueryable
、IQueryProvider接口 -
3.1.延迟加载IEnumertor
对象(提高系统性能) -
3.2.扩展方法的扩展对象之奥秘(this IQueryable
source) -
3.3.分段执行IQueryable
中的子方法(Queryable中的扩展方法) -
3.4.链式查询方法的设计误区(重点:一次执行程序多次处理)
-
3.5.环路执行对象模型、碎片化执行模型(假递归式调用)
-
3.6.N层对象执行模型(纵横向对比链式扩展方法)
-
3.7.LINQ查询表达式和链式查询方法其实都是空壳子
-
3.8.详细的对象结构图(对象的执行原理)
-
3.9.IQueryable
与IQueryProvider一对一的关系能否改成一对多的关系
-
-
4.完整的自定义查询
1】.开篇介绍
在开始看本篇文章之前先允许我打断一下各位的兴致。其实这篇文章本来是没有打算加“开篇介绍”这一小节的,后来想想还是有必要反馈一下读者的意见。经过前三篇文章的详细讲解,我们基本上对LINQ框架的构成原理有了一个根本的认识,包括对它的设计模型、对象的模型等,知道LINQ的查询表达式其实是C#之上的语法糖,不过这个糖确实不错,很方便很及时,又对一系列的LINQ支撑原理进行了大片理论的介绍,不知道效果如何;
在结束上一篇文章的时候,看到一个前辈评论说建议我多写写LINQ使用方面的,而不是讲这些理论。顺便借此机会解释一下,本人觉得LINQ的使用文章网上铺天盖地,实在没有什么必要更没有价值去写,网上的LINQ使用性的文章从入门到复杂的应用实在是太多了,不管是什么级别的程序员都能找到适用的文章。我更觉得这些文章属于使用类的,在实际项目中用到的时候稍微的查一下能用起来就行了,而重要的是能搞懂其原理才是我们长期所追求的,因为这些原理在任何一个应用框架的设计中都是相通的,可以帮助我们举一反三的学习,减少学习成本,不断的提高内在的设计思想。
所谓设计能力体现技术层次,这句话一点都不假。同志们我们不断追求的应该是设计,而不是拿来就用。当你搞懂了原理之后,我想每个人都能想出来各种不同的应用方向,那么技术发展才有意义,当然这也是最难能可贵的。[王清培版权所有,转载请给出署名]
2】.扩展Linq to Object (应用框架具有查询功能)
我们知道LINQ所支持的查询范围主要在IEnumerable
LINQ查询Object是基于IEnumerable
对象的,不是集合对象有什么好查的。对于IEnumerable 对象的LINQ查询是Enumerable静态对象在支撑着,然后通过匿名表达式来表示逻辑,这样就能顺其自然的查询集合。那么我们该如何下手扩展Linq to Object?其实也就是两点可以扩展,要么提供扩展方法来扩展IEnumerable 对象,当然你别企图想让VS支持某种关键字让你对应扩展方法。还有就是继承IEnumerable 对象让我们自己的集合类型具备LINQ的强类型的查询能力。当然具体要看我们需求,从技术角度看目前只有这两点可以扩展。 如果我们使用扩展方法那么只能是扩展IEnumerable
对象,这没有问题。我们可以很方便的在LINQ的表达式中调用我们自己的扩展方法,让自己的方法跟着一起链式查询。如果我们从继承IEnumerable 对象扩展,那么情况会有点小复杂,你的扩展方法中要扩展的对象一定要具体的给出对象的定义才行,如果你扩展的对象不能和继承的对象保持一直,那么你将断掉所有的扩展方法。[王清培版权所有,转载请给出署名]
2.1】.通过添加IEnumerable对象的扩展方法
下面我们通过具体的例子来分析一下上面的理论,先看看通过扩展方法来扩展系统的IEnumerable
代码段:Order类
- ///
- /// 订单类型
- ///
- public class Order
- {
- ///
- /// 订单名称
- ///
- public string OrderName { get; set; }
- ///
- /// 下单时间
- ///
- public DateTime OrderTime { get; set; }
- ///
- /// 订单编号
- ///
- public Guid OrderCode { get; set; }
- }
这是个订单类纯粹是为了演示而用,里面有三个属性分别是"OrderName(订单名称)"、"OrderTime(下单时间)"、"OrderCode(订单编号)",后面我们将通过这三个属性来配合示例的完成。
如果我们是直接使用系统提供的IEnumerable
再发散一下思维,我们甚至可以在扩展方法中做很多文章,把扩展方法纳入系统架构分析中去,采用扩展方法封装流线型的处理逻辑,对业务的碎片化处理、验证的链式处理都是很不错的。只有这样才能真正的让这种技术深入人心,才能在实际的系统开发当中去灵活的运用。
下面我们来构建一个简单的IEnumerable
代码段:OrderCollectionExtent静态类
- public static class OrderCollectionExtent
- {
- public static bool WhereOrderListAdd
( this IEnumerableIEnumerable) where T : Order - {
- foreach (var item in IEnumerable)
- {
- if (item.OrderCode != null && !String.IsNullOrEmpty(item.OrderName) && item.OrderTime != null)
- {
- continue;
- }
- return false;
- }
- return true;
- }
- }
OrderCollectionExtent是个简单的扩展方法类,该类只有一个WhereOrderListAdd方法,该方法是判断当前集合中的Order对象是否都满足了插入条件,条件判断不是重点,仅仅满足例子的需要。这个方法需要加上Order类型泛型约束才行,这样该扩展方法才不会被其他的类型所使用。
- List
orderlist = new List() - {
- new Order(){ OrderCode=Guid.NewGuid(), OrderName="水果", OrderTime=DateTime.Now},
- new Order(){ OrderCode=Guid.NewGuid(), OrderName="办公用品",OrderTime=DateTime.Now}
- };
- if (orderlist.WhereOrderListAdd())
- {
- //执行插入
- }
如果.NET支持扩展属性【不过微软后期肯定是会支持属性扩展的】,就不会使用方法来做类似的判断了。这样我们是不是很优雅的执行了以前BLL层处理的逻辑判断了,而且这部分的扩展方法是可以动态的更改的,完全可以建立在一个独立的程序集当中。顺便在扩展点使用思路,在目前MVVM模式中其实也可以将V中的很多界面逻辑封装在扩展方法中来减少VM中的耦合度和复杂度。包括现在的MVC都可以适当的采用扩展方法来达到更为便利的使用模式。
但是大部分情况下我们都是针对所有的IEnunerale
2.2】.通过继承IEnumerable接口
我想大部分的情况下我们都是直接使用IEnumerable
我们继续看例子,该例子是针对继承IEnumerable
- public class OrderCollection : IEnumerable
- {
- List
orderList; - public OrderCollection()
- {
- orderList = new List
() { - new Order(){ OrderCode=Guid.NewGuid(),OrderName="订单1", OrderTime=DateTime.Now},
- new Order(){ OrderCode=Guid.NewGuid(),OrderName="订单2", OrderTime=DateTime.Now},
- new Order(){ OrderCode=Guid.NewGuid(),OrderName="订单3", OrderTime=DateTime.Now}
- };
- }
- public IEnumerator
GetEnumerator() - {
- foreach (var order in orderList)
- {
- yield return order;
- }
- }
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- foreach (var order in orderList)
- {
- yield return order;
- }
- }
- }
这是个Order集合类型OrderCollection类,该类专门用来存放或处理Order类的。不管是从兼容.NET2.0或者其他方面考虑都可能将集合的类型封装在.NET2.0版本的程序集中,在.NET2.0之上的版本都会提供扩展版本的程序集,这个时候我们的扩展方法要专门针对OrderCollection去编写,否则就会造成 IEnumerable
- public static OrderCollection GetOutOrderCollection(this OrderCollection OrderColl)
- {
- return OrderColl;
- }
这个时候会很干净的使用着自己的扩展方法,不会造成大面积的污染。当然一般都是依赖倒置原则都会有高层抽象,不会直接扩展实现类,这里只是简单的介绍。[王清培版权所有,转载请给出署名]
2.3】.详细的对象结构图
这个小结主要将IEnumerable
对象静态模型、运行时导图:
上图中的关键部分就是i==10将被封装成表达式直接送入Where方法,而select后面的i也是表达式【(int i)=>i】,也将被送入Select方法,这里就不画出来了。顺着数字序号理解,IEnumerable
小结:本节主要讲解了Linq to Object的原理,其实主要的原理就是Lambda表达式传入到Enumerable扩展方法当中,然后形成链式操作。Linq 只是辅助我们快速查询的语言,并不是.NET或者C#的一部分,在任何.NET平台上的语言中都可以使用。下面我们将重点分析Linq to Provider,这样我们才能真正的对LINQ进行高级应用。[王清培版权所有,转载请给出署名]
3.】.实现IQueryable 、IQueryProvider接口
这篇文章的重点就是讲解IQueryable
IQueryable
接口是Linq to Provider的入口,非常有意思的是它并不是一个IQueryable 来支撑一次查询。我们在编写Linq语句的时候一般都是 where什么然后select 什么,至少连续两个扩展方法的映射调用,但是朋友你知道它内部是如何处理的吗?每当Where过后紧接着Select他们是如何关联一个完整的查询的?IQueryable 并非IEnumerable 对象,无法实时的做出处理然后将结果返回给下一个方法接着执行。那么它如何将片段性的执行方法串成一个整的、完整的查询?下面我们将逐个的分析这其中要涉及到的模式、数据结构、框架原则,这些搞懂了之后代码都是模型的表现,也就顺其自然的明白了。
3.1】.延迟加载IEnumertor对象(提高系统性能)
延迟加载的技术其实在Linq之前就已经在使用,只不过很少有人去关注它,都被隐藏在系统框架的底层。很多场合下我们需要自己去构建延迟加载特性的功能,在IEnumerable
- public IEnumerator
GetEnumerator() - {
- return (Provider.Execute
(Expression) as IEnumerable).GetEnumerator(); - }
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();
- }
这是IQueryable
我们用图来分析一下Linq to Provider中的延迟加载的原理;
这段代码不会被立即执行,我们跟踪一下各个组成部分之间的执行过程;
这幅图重点是IQueryable
最后Orderlist将是一个IQueryable
3.2】.扩展方法的扩展对象之奥秘(this IQueryable source)
其实这里有一个思维陷阱,当我们分析源码的时候只将焦点集中在扩展方法中的后面参数上,而没有集中精力考虑扩展方法所扩展的对象本身,看似不同的方法位于不同的地方,其实他们来自一个地方,所在的逻辑对象是一个,但是这恰恰会造成我们分析问题的瓶颈,这里我们重点的讲解一下扩展方法所扩展对象。
我们直接用源码进行讲解吧;
- public static IQueryable
Select this IQueryable( source, Expression > selector) - {
- if (source == null)
- {
- throw Error.ArgumentNull("source");
- }
- if (selector == null)
- {
- throw Error.ArgumentNull("selector");
- }
- return source.Provider.CreateQuery
(Expression.Call( null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(selector) }));- }
这是Queryable类中的Select扩展方法的源代码,它扩展IQueryable
3.3】.分段执行IQueryable中的子方法(Queryable中的扩展方法)
都知道Linq的查询是将一些关键字拼接起来的,行成连续的查询语义,这其中背后的原理文章上上下下也说过很多遍,我想也应该大致的了解了。其实这有点像是把大问题分解成多个小问题来解决,但是又不全是为了分解问题而这样设计,在链式查询中很多关键字在不同的查询上下文中都是公用的,比如where可以用在查询,也可以用在更新、删除。这里讨论的问题可能已经超过LINQ,但是很有意义,因为他们有着相似的设计模型。
根据3.2图中的意思,我们都已经知道扩展方法之间传输的对象都是来自不同的实例但是来自一个对象类型,那么为什么要分段执行每个关键字的操作呢?我们还是用图来帮助我们分析问题吧。
两行代码都引用了Where方法,都需要拼接条件,但是 Where方法所产生的条件不会影响你之前的方法。分段执行的好处就在这里,最大粒度的脱耦才能最大程度的重用。
3.4】.链式查询方法的设计误区(重点:一次执行程序多次处理)
在使用IQueryable
- public static IQueryable
Where this IQueryable( source, Expression bool>> predicate) - {
- if (source == null)
- {
- throw Error.ArgumentNull("source");
- }
- if (predicate == null)
- {
- throw Error.ArgumentNull("predicate");
- }
- return source.Provider.CreateQuery
(Expression.Call( null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) }));- }
类似这段代码的在文章的上面曾出现过,大同小异,我们下面详细的分析一下它的内部原理,到底是如何构建一个动态却是静态的对象模型。
这个方法有一个参数,是条件表达式,并且这个方法扩展IQueryable
那么重点是最后一行代码,它包裹着几层方法调用,到底是啥意思呢?我们详细分解后自然也就恍然大悟了。
由于问题比较复杂,这里不做全面的IQueryable
面向接口的设计追求职责分离,这里为什么把执行和创建IQueryable