我们知道实现了IQueryable<T>接口和IQueryProvider接口就可以使用Linq To SQL的功能。关于如何去实现的话,上一章也为我们引导了一个方向。LinqToDB框架也是顺着这个方向进行的。然而笔者对LinqToDB框架的作者真的很无语。如果有打开过LinqToDB框架源码的朋友,可能会发现很多代码都没有文字说明。这无疑给那些想要深入了解框架的人加大了前进力度。本来笔者以为只是没有相关代码说明不用怕。只要找到对应的文档应该没有什么大问题。于是笔者也跟很多人一样子——去作者的github上找。结果只有教大家如何使用。却对框架没有进行任何说明。笔者又想也许作者比较懒吧。可能只有在他的博客上才有相关的文档吧。结果笔者很失望——没有找到任何有帮助的东西。唉!为了能获得更多的资料笔者还购买了VPN。所以想要了解LinqToDB框架只有一个方式——硬着头皮啃代码。显然这种做法就是从代码中寻找作者的思路和理念。同时不可否认存在猜测上的错误。必竟作者的随意一段代码就有可能导至理解上的误导。希望读者们能够理解。
LinqToDB框架的原理
正如上面所讲的,LinqToDB框架也是按上一章中讲到的三个大至步骤进行的。所以我们的目标也变得很明确——了解LinqToDB框架是如何实现这三大步骤的。
1.实现Linq提供的IQueryable<T>接口和IQueryProvider接口。生成相关的表达式树。 2.把对应的表达式树转化生成对应数据库的SQL语句。并执行。 3.根据映射的信息,生成对应的集合类。
从上一章的例子中我们可以看到LinqToDB框架以DataContext类作为入口类。这一点显然跟Entity Framework一样子。都是以某个类做为上下文引动整个框架。如果你们看过github上的例子的话,就会发现笔者用的入口类跟作者不太一样子。作者用的是DataConnection类。事实上不管是DataConnection类还是DataContext类他们都继承了IDataContext接口。所以在使用上来讲,不会差太多。只是在笔者看来他们之间的职责却存在很大的差别。不过笔者还是不太明白作者这样子设计的目的。DataConnection类能做DataContext类的大部分事情,却又拥有自己独有的职责——负责存取当前数据库的信息。让笔者感觉DataConnection类在职责上有一点重复了。这也是为什么笔者认为DataContext类才是LinqToDB框架入口类。至少让笔者觉得DataContext类比较明确。
上面这张图片是描述了LinqToDB框架在查询时候的一个原理图。是笔者根据代码的运行路线整理出来的。主要的目地就是为了方便导引大家深入,少走一点弯路。我们知道要实现Linq查询很简单,就是上面的三个步骤。可是要实现这三个步骤的事情却很多。图片上只是显示主要核心类切口。可以说这三个步骤就是靠图片上的类进行工作的。
加载数据数库信息
LinqToDB框架并没有直接就将实现IQueryable<T>和IQueryProvider的类交出来。而是以上下文的思维方式间接性的引导出来。这也是合理的设计。必竟在生成表达式树和SQL语句之前,我们有必要知道数据库的相关信息。比如当前数据库是用什么——Sql Server还是MySql。而这个工作任务交给图中的DataConnection。这味意着LinqToDB框架在进入上面所讲的第一步的时候,就已经知道数据库的信息了。但是作者并没有直接性的设置数据库的信息。而是通过IDataProvider接口实例来提供。这样子更加区别出DataContext类只是用于引导的职责。如果你们使用过增删改的话,你们可能会觉得不对。他不是可以会增删改吗?事实上如果你们用心点的话,就会现他们都是静态扩展方法。
生成表达式树
通过DataContext类我们就可以拿到我们的集合表(这里的集合表是指实现ITable接口的实例)。ITable接口作用跟Entity Framework的IDbSet接口很相近。从代码中我们可能看到他来自于IExpressionQuery接口。相信大家看到名字就明白IExpressionQuery接口的实现类就是图片中的ExpressionQuery类。这个时候上面说的第一步工作开始进行了。ExpressionQuery类就是对应的实现IQueryable<T>接口和IQueryProvider接口的类。当然,LinqToDB框架用的是IQueryable<T>接口的子接口——IOrderedQueryable<T>。生成表达式树的工作也在这里进展开始了。
执行数据库
要实现Linq查询的功能主要难点在如何去处理表达式树,在通过表达式树生成对应的T-SQL。LinqToDB框架通过一个核心类Query来作中间过度。笔者喜欢把他叫中间者。作者在设计用到Query类时候,并不是把他直接实例出来。而是通过ExpressionBuilder类进行进一步的加工,把相关的信息分配来Query类实例,在返回Query类实例。那么对于ExpressionBuilder类的职责笔者用一俩句话是很难说清楚。这里只能大概讲他是用于处理表达式树的。其中不得不用到一个叫SelectQuery类的。这个类很重要。他会参与最后生成T-SQL工作里面去。笔者想说通过ExpressionBuilder类加工之后,对应的是查询还是增删改都会记录在SelectQuery类实例里面。就相当于生成T-SQL的信息都存放在SelectQuery类实例里面。
Query类的工作职责比较复杂。当经历过处理表达式树的过程之后,Query类就具备生成T-SQL的能力了。为什么这样子讲呢?这个时候Query类拥有生成T-SQL的SelectQuery类实例和结果集的映射信息MapInfo类实例。所以生成T-SQL也只是一个时间问题。在生成T-SQL的这个过程看起来简单,事实上却有很多细节要处理。作者让LinqToDB框架通过DataConnection类去调用BasicSqlBuilder类的子类来处理生成T-SQL。当然这一个过程里面离不开SelectQuery类。只是作者并不是直接提交T-SQL,而是生成一个叫PreparedQuery类实例。PreparedQuery类用于存放执行数据库的信息。这也是最后一步了。通过PreparedQuery类实例生成XxxCommand类执行数据库。在通过MapInfo类实例转化为对应的集合数据。
结束语
好了。对于LinqToDB框架的原理笔者就介绍在这里。本系列的后面章节也是依据本章的思路进行的。看看作者是什么样子设计LinqToDB框架的。又有什么值得我们去学习的。