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類的作用也會跟着介紹出來。