LinqToDB 源码分析——DataContext类

LinqToDB框架是一个轻量级的ORM框架。当然,功能上来讲一定比不上Entity Framework的强大。但是在使用上总让笔者感觉有一点Entity Framework的影子。笔者想过可能的原因有俩点:一是DataContext类的作用跟DbContext的作用很接近;二是都实现Linq查询的功能。那么DataContext类到底在LinqToDB框架里面算什么呢?笔者把DataContext类理解为这个框架的上下文——用于驱动整个LinqToDB框架。所以本章也是为DataContext类而来。

框架配置


 从源码里面我们可以看到DataContext类有三个构造函数。笔者也是根据这三个构造函数来推断出LinqToDB框架可存在多种启动方式。这里面最大的亮点不是构造函数而是他的参数名。如下代码。

public DataContext(): this(DataConnection.DefaultConfiguration);
public DataContext(string configurationString);
public DataContext(IDataProvider dataProvider,string connectionString);

上面代码有俩个参数名很重要——configurationString和connectionString。如果把他们都译过来的话,就是配置字符串和连接字符串。相信不难看出configurationString就是跟配置文件画上关系。而connectionString就是传入连接字符串的意思。本质来讲实现DataContext类只有俩种方式:一种是通过配置文件(如App.config)来实现;一种是用IDataProvider接口实例和连接字符串来实现。如果你什么也不传的话,就会使用默认的配置信息来实现。

LinqToDB框架根据.NET配置机制实现自定义配置。跟配置有关系的类都存放在LinqToDB.Configuration命名空间下。如果要实现.NET配置机制的话,就必须要有一个实现ConfigurationSection的类。LinqToDBSection类就是要我们要找的类了。作者用单例模式来设计LinqToDBSection类。相信大家都能明白作者的目地。那么LinqToDB框架是什么时候加载配置信息的呢?

注意:LinqToDBSection有俩个属性一个子点。DefaultConfiguration属性用于指定默认配置字符串。DefaultDataProvider属性用于指定默认数据提供者。还有叫dataProviders的子节点。关于dataProviders节点笔者希望大家不要去用。主要是笔者觉得作者这边写的有一点问题。当然有兴趣的朋友可以去看看。

 LinqToDB框架启动本质上来讲就是实例化DataContext类成功了。从上面的三个构造函数我们可以看出在实例化DataContext类的时候,就必须传入一个配置字符串的参数。对于配置字符串来讲,你可以手动指定或是设置对应的配置信息。不管是哪一种最终都会去调用DataConnection类。哪怕你什么都不传的情况下,LinqToDB框架也会通过DataConnection.DefaultConfiguration来获得默认的配置字符串。笔者要讲的是就是这个时候开始加载配置信息。而这一切都将交给DataConnection类来负责。

 1 static DataConnection()
 2         {
 3             _configurationIDs = new ConcurrentDictionary<string, int>();
 4 
 5             LinqToDB.DataProvider.SqlServer.SqlServerTools.GetDataProvider();
 6             LinqToDB.DataProvider.Access.AccessTools.GetDataProvider();
 7             LinqToDB.DataProvider.SqlCe.SqlCeTools.GetDataProvider();
 8             LinqToDB.DataProvider.Firebird.FirebirdTools.GetDataProvider();
 9             LinqToDB.DataProvider.MySql.MySqlTools.GetDataProvider();
10             LinqToDB.DataProvider.SQLite.SQLiteTools.GetDataProvider();
11             LinqToDB.DataProvider.Sybase.SybaseTools.GetDataProvider();
12             LinqToDB.DataProvider.Oracle.OracleTools.GetDataProvider();
13             LinqToDB.DataProvider.PostgreSQL.PostgreSQLTools.GetDataProvider();
14             LinqToDB.DataProvider.DB2.DB2Tools.GetDataProvider();
15             LinqToDB.DataProvider.Informix.InformixTools.GetDataProvider();
16             LinqToDB.DataProvider.SapHana.SapHanaTools.GetDataProvider();
17 
18             var section = LinqToDBSection.Instance;
19 
20             if (section != null)
21             {
22                 DefaultConfiguration = section.DefaultConfiguration;
23                 DefaultDataProvider = section.DefaultDataProvider;
24 
25                 foreach (DataProviderElement provider in section.DataProviders)
26                 {
27                     var dataProviderType = Type.GetType(provider.TypeName, true);
28                     var providerInstance = (IDataProviderFactory)Activator.CreateInstance(dataProviderType);
29 
30                     if (!string.IsNullOrEmpty(provider.Name))
31                         AddDataProvider(provider.Name, providerInstance.GetDataProvider(provider.Attributes));
32                 }
33             }
34         }

这是DataConnection类的静态构造构函数。我们可以看出他做了俩件事情:一是加载框架里面所有的数据提供者;二是加载配置文件上的信息。对于数据提供者下面会介绍啊。我们来看一下加载配置相关的内容。如下

1.设置默认的配置字符串和数据提供者名称
2.如果配置文件上存在数据提供者的话,就加载对应的数据提供者工厂(IDataProviderFactory)。然后通过工厂来获得数据提供者。

通过上面的了解,是不是说明在实例化DataContext类的时候,什么参数都不传的情况下,我们就一定要在配置文件里面指定默认的配置字符串呢?本来笔者以为如果你在配置文件不指定的话,LinqToDB框架就会报错误。可是让笔者傻眼的是完全没有问题。这又是什么一会事情呢?看一下笔者例子的源码吧。

配置代码:

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3   <configSections>
 4     <section name="linq2db" type="LinqToDB.Configuration.LinqToDBSection, linq2db" requirePermission="false" />
 5   </configSections>
 6   <!--<linq2db defaultConfiguration="Aomi"  />-->
 7   <connectionStrings>
 8     <add name="Aomi"   connectionString="Data Source=.;Initial Catalog=Northwind;User ID=sa;Password=123" providerName="System.Data.SqlClient" />
 9   </connectionStrings>
10   <startup>
11     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
12   </startup>
13 </configuration>

上下文代码:

public class AdoContext : DataContext
{
    public ITable<Products> Products
    {
        get
        {
             return this.GetTable<Products>();
        }
    }
}

例子中我们可以看到笔者配置了连接字符串却注解掉了指定的默认配置字符串。同时AdoContext类调用的是默认的构造函数。执行很成功。笔者却久久不能闭眼。为什么呢?事实上除了上面讲到的静态构造构函数(DataConnection类)里面会去设置默认配置字符串。作者又发神精的在另一个小小的地方也做了同样子的事情——InitConnectionStrings方法。

 1 static void InitConnectionStrings()
 2 {
 3    foreach (ConnectionStringSettings css in ConfigurationManager.ConnectionStrings)
 4    {
 5         _configurations[css.Name] = new ConfigurationInfo(css);
 6 
 7          if (DefaultConfiguration == null && !IsMachineConfig(css))
 8          {
 9                 DefaultConfiguration = css.Name;
10           }
11    }
12 }

InitConnectionStrings方法是用于处理配置连接字符串。同时又多做了一件事情判断默认配置字符串是否存在。如果他不存在的话,就是连接字符串的Name给他。那么InitConnectionStrings方法也是在实例化DataContext类的时候进行的。详细如下。

到了这里相信对LinqToDB框架配置方面就有了一定的了解。有了配置信息,自然就知道去哪里找连接字符串。同时也就明白了去哪里查找数据库的信息。

数据供应者


在做Linq查询之前,对于数据库的信息,LinqToDB框架还是有必要知道的。作者充分的考虑过框架将来是要为多种数据库服务的。所以多出了一个角色叫数据提供者。不同的数据库就有相应的数据提供者。从设计的角度来讲可以说是提供者模式。所有的数据提供者类在LinqToDB.DataProvider命名空间下。他们有一个共同的基类叫IDataProvider接口。但是不是直接实现接口,而是继承DataProviderBase类。如图下。

数据供应者在Linq查询过程中,DataProvider起了举足轻重的作用。笔者先暂停介绍他的作用。让我们看看他是出自哪里的。从上面DataConnection类的静态构造构函数里面,我们会发现他加载配置信息的同时也会初始化框架里所有的数据提供者。为了方便笔者把初始化数据提供者部分的代码贴在下面。

LinqToDB.DataProvider.SqlServer.SqlServerTools.GetDataProvider();
LinqToDB.DataProvider.Access.AccessTools.GetDataProvider();
LinqToDB.DataProvider.SqlCe.SqlCeTools.GetDataProvider();
LinqToDB.DataProvider.Firebird.FirebirdTools.GetDataProvider();
LinqToDB.DataProvider.MySql.MySqlTools.GetDataProvider();
LinqToDB.DataProvider.SQLite.SQLiteTools.GetDataProvider();
LinqToDB.DataProvider.Sybase.SybaseTools.GetDataProvider();
LinqToDB.DataProvider.Oracle.OracleTools.GetDataProvider();
LinqToDB.DataProvider.PostgreSQL.PostgreSQLTools.GetDataProvider();
LinqToDB.DataProvider.DB2.DB2Tools.GetDataProvider();
LinqToDB.DataProvider.Informix.InformixTools.GetDataProvider();
LinqToDB.DataProvider.SapHana.SapHanaTools.GetDataProvider();

我们可以看到对应的每一个数据库都会一个相应的XxxTools类。XxxTools类里面包含了大量的静态方法。这个时候笔者又不得不把DataConnection类在拿出来讲。为什么呢?DataConnection类里面有一个叫_dataProviders的集合属性。他是静态的。主要用于存在当前拥有的数据提供者。所以上面这段代码初始化之后,所有的数据提供者都会存放在DataConnection类里面。

上面的代码执行完成之后,我们就拥有了所有的数据提供者。但最后LinqToDB框架还是会根据当前指定的配置字符串找到对应的数据提供者。这过程中会让一个叫ConfigurationInfo类来帮忙。ConfigurationInfo类就是用于存放从配置文件来的配置信息。ConfigurationInfo类里面有一个数据提供者属性DataProvider。通过这个属性我们就可以获得当前的数据库对应的数据提供者。还是笔者画来一张图片来形容这个过程吧。

注意:x.x.x 表示有三层,每一层按数字大小执行,相应数字后面的层必须先执行。比如有1.2和1.2.1就必须先把1.2.1执行完才能去执行1.2。又如1.1和1.2就是1.1执行完去执行1.2

DataContext类想要知道数据库的信息。就必须通过数据提供者来获得。所以就会在构造函数里面调用DataConnection类的GetDataProvider方法。但是这一步开始之前一定会先执行DataConnection类的静态构造函数。DataConnection类的静态构造函数里面又去调用所有XxxTools类的GetDataProvider方法。相应的又必须先调了XxxTools类的静态构造函数。相应的结果之后才会去执行DataConnection类的GetDataProvider方法。接下就看上面图片的。

当我们拿到了对应的数据提供者之后,DataContext类就是通过数据提供者获得相应的配置字符串、数据提供者的名称和结构映射。这些都是DataContext类的构造函数里面知道的。如果只是这么简单的话,那么笔者肯定不爽。事实上数据提供者还会参与后面的工作。让笔者先拿出一部分的代码来介绍一下数据提供者还有一些什么功能。

 1 /// <summary>
 2     /// 数据供应者
 3     /// </summary>
 4     public interface IDataProvider
 5     {
 6         /// <summary>
 7         /// 供应者的名称
 8         /// </summary>
 9         string Name { get; }
10         /// <summary>
11         /// 连接类的空间命名
12         /// </summary>
13         string ConnectionNamespace { get; }
14         /// <summary>
15         /// 对应DataReader类的类型
16         /// </summary>
17         Type DataReaderType { get; }
18         /// <summary>
19         /// 获得当前的结构映射
20         /// </summary>
21         MappingSchema MappingSchema { get; }
22         /// <summary>
23         /// 有一点像是标记数据库的状态
24         /// </summary>
25         SqlProviderFlags SqlProviderFlags { get; }
26         /// <summary>
27         /// 新建一个数据库连接
28         /// </summary>
29         /// <param name="connectionString"></param>
30         /// <returns></returns>
31         IDbConnection CreateConnection(string connectionString);
32         /// <summary>
33         /// 新建一个生成T-SQL的SQL生成类
34         /// </summary>
35         /// <returns></returns>
36         ISqlBuilder CreateSqlBuilder();
37         /// <summary>
38         /// 新建一个优化SQL的优化类
39         /// </summary>
40         /// <returns></returns>
41         ISqlOptimizer GetSqlOptimizer();
42         /// <summary>
43         /// 初始化一个Command命令。Command就是如SqlCommand之类的。
44         /// </summary>
45         /// <param name="dataConnection"></param>
46         /// <param name="commandType"></param>
47         /// <param name="commandText"></param>
48         /// <param name="parameters"></param>
49         void InitCommand(DataConnection dataConnection, CommandType commandType, string commandText, DataParameter[] parameters);
50         /// <summary>
51         /// 删除一个Command命令
52         /// </summary>
53         /// <param name="dataConnection"></param>
54         void DisposeCommand(DataConnection dataConnection);
55 
56          //.....
57          //.....
58          //.....
59          //.....
60 
61     }        

最后一定会去执行数据库,那么获得数据库连接是肯定有的。所以我们可以从上面看到新建一个数据库连接的功能。同时数据提供者又带有新建生成T-SQL的功能类。笔者上面有提到结构映射(MappingSchema类)这个功能类。他的作用就是帮我们决定语言之间的类型问题。必竟C#和SQL类型上还是有一定的差别的。每执行一个T-SQL语句相应的会有一个Command命令。这一部分的工作也是有数据提供者的职责。看样子数据提供者真的很忙。

我们先大体上的了解一个数据提供者的作用。因为本章主要是DataContext类相关的知道点。而且关于数据提供者还有要结合后面的代码功能才能更加深入的了解。所以本章对应数据提供者就讲到这里。

结束语


DataContext类源码里面还包含了一些对数据库操作的方法,我们会发现他本身好像拥有对数据库增删改的功能。可是笔者并没有在本章中介绍到,这并不意味着结束。事实上DataContext类都在后面的章节出动不动的出显。所以相应的关于DataContext类的作用也会跟着介绍出来。

原文地址:https://www.cnblogs.com/hayasi/p/6059922.html