LinqToDB 源碼分析——前言


記得筆者進入公司的時候接觸的第一個ORM框架是Entity Framework。為了Entity Framework也看了不些的英文資料(不是筆者裝B哦)。正式使用三個月后。筆者對他有一個全面性的認識。我只能說他真的很強大,也很方便。可是我並不是很喜歡他。要問為什么的話,筆者只能說喜歡就是喜歡。不喜歡就是不喜歡。不需要過多的理由。筆者就是這樣子的一個人。但是筆者不會忽略他的強大的一面。微軟的目標還是老樣子——開發簡單化。只是在Entity Framework的數據遷移上面筆者不是很喜歡。至少在筆者團隊開發過程常常會出現因版本不對導至數據丟失。不管如何筆者對Entity Framework的使用也至少有一年的時間。由於項目以領域驅動(DDD)為核心思想。所以在設計的時候,會用到一些筆者覺得還不錯的思想。比如工作單元(Unit Of Work模式)。Entity Framework在早期的時候是不開源碼。筆者以前是從事JAVA開發的。這對筆者來講心態上有一點不能接受(當然這也是筆者個人心態)。終於Entity Framework6開源碼了。如果有興趣的朋友可以下載下來看看(源碼地址:https://github.com/aspnet/EntityFramework6)。

第二年時候筆者接觸了第二個ORM框架是LinqToDB。筆者不是想強調LinqToDB有多么好。筆者只是覺得他是一個相當不錯的開源ORM框架。功能不比EF差,用法上很接近的EF,卻比EF來得輕量,而且又多出了自己的特色。所以如果你用EF用得有一點煩了或是覺得EF有一點笨重。想去看看有沒有別的ORM框架。不煩試試LinqToDB。

開發環境


對於LinqToDB的dll包在NuGet上可以下載到。只要輸入“linq2db”即可。同時也可以在Github上面下載(https://github.com/linq2db/linq2db)。最好選擇跟筆者一樣子的版本,比較穩定。如下

軟件開發工具:Visual Studio 2013

LinqToDB版本:linq2db-Release.1.0.7.4

數據庫:SQL Server 2008R

LinqToDB介紹


LinqToDB做為一個輕量級的ORM框架。當然可以讓開發人員用面向對象的思想來操作數據庫。而且他基於是Linq上面進行開發的。所以一般的Linq操作他也是支持的。同時作者又擴展對應的DML和DDL。比如增加 Insert, Delete, Update, CreateTable, DropTable等方法。相對於EF來講,LinqToDB顯得還是很弱小,沒有那么強大。LinqToDB可以說只是把Linq動作變成對應的SQL語句。然后在進行操作數據庫。這顯然更加接近原生態的做法。也是筆者為什么喜歡的點之一。那么LinqToDB到底能支持多少種數據庫。作者在Github上也做也明確指出來。如下。

LinqToDB是如何使用呢?作者在Github上面用了經典的Northwind數據庫來講解。不如筆者也來用一下Northwind數據庫進行講解本系列的一些試驗和列子。Northwind數據庫是Sql Server 2000數據庫的經典設計的數據庫。如果不懂的朋友,請百度一下。我們都知道EF有三種模式開發。那么是不是意味着LinqToDB也有可能有這三種開發呢?對於這一點作者也沒有很明確的說明。LinqToDB並沒有像EF那樣子可以根據設計好的類來生成對應的數據庫表結構。只能說目前LinqToDB有倆種方式來進行開發——一種原生態的代碼,一種根據TT模板。原生態的代碼就是數據庫建完之后,配置對應的映射,然后自己業務操作。根據TT模板就是用TT模板生成數據庫對象映射。這倆種方式筆者會更加的喜歡前一種。

LinqToDB和EF有一個相類似點。他們都有一個關鍵的類。這個類拉動了所有動作的上下文。如果說DbContext類是EF的核心,那么DataContext類便是LinqToDB的重心。DataContext類的作用跟DbContext類在EF框架里面的作用很接近。使得只有用過EF的人對於LinqToDB有一種親近感。

 1 using LinqToDB;
 2 using LinqToDB.DataProvider.SqlServer;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 
 8 namespace LinqToDBExample
 9 {
10     public class DbNorthwind:DataContext
11     {
12         public DbNorthwind()
13             : base(SqlServerTools.GetDataProvider(SqlServerVersion.v2008), "Data Source=.;Initial Catalog=Northwind;User ID=sa;Password=123")
14         { 
15 
16         }
17 
18         public ITable<Products> Products { get { return this.GetTable<Products>(); } }
19 
20     }
21 }

筆者新建一個類DbNorthwind。讓這個類繼承DataContext類。同時增加了獲得Northwind數據庫中dbo.Products表對應的關系類屬性Products。這個屬性是一個ITable接口。當然跟EF的IDbSet接口有一曲同工之妙。是不是覺得像EF找到了失散多年的兄弟。而對映射配置上那些事情只怕看完之后,基本上你會跟筆者一樣子淡定了許多。也許你很少用到EF的注解配置,但是這不能代表他們倆者之間不存在相似之處。

 1 using LinqToDB.Mapping;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Linq;
 5 using System.Text;
 6 
 7 namespace LinqToDBExample
 8 {
 9     public class Products
10     {
11         [PrimaryKey, Identity]
12         public int ProductID { set; get; }
13 
14         [Column(Name = "ProductName"), NotNull]
15         public string Name { get; set; }
16     }
17 }

看到了吧。可以說沒有什么新的知識。即使沒有用過EF,也可以從關鍵字中得到對應的信息。筆者也相信關鍵字PrimaryKey你在Sql Server中一定會有用到過。即是主鍵的意思。這時候來一個簡單的查詢應該是一件非常棒的事情。可以讓我們看到他在查詢是如何的表現。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 
 6 namespace LinqToDBExample
 7 {
 8     class Program
 9     {
10         static void Main(string[] args)
11         {
12             using(var db = new DbNorthwind())
13             {
14                 var query = from p in db.Products
15                             where p.ProductID > 25
16                             orderby p.Name descending
17                             select p;
18 
19                 foreach (Products product in query.ToList())
20                 {
21                     Console.WriteLine(string.Format("ProductID:{0} ------------ProductName:{1}", product.ProductID, product.Name));
22                 }
23 
24             }
25 
26             Console.ReadKey();
27         }
28     }
29 }

執行結果:

顯然,LinqToDB在使用的語法跟EF很接近。正如筆者所講的——如果你真的不太喜歡EF的話,可以試着用一下LinqToDB吧。也許你可以看到另一片天空也說不一定。

如果你覺得上面DataContext類的構造函數用法有一點煩。不擔心讓筆者在介紹一種。通常我們在開發的時候會用到App.config或是Web.config。而對於connectionStrings節點相信大家並不陌生。筆者要介紹這種便是使用配置文件來完成。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="linq2db" type="LinqToDB.Configuration.LinqToDBSection, linq2db" requirePermission="false" />
  </configSections>
  <linq2db defaultConfiguration="Aomi" />
  <connectionStrings>
    <add name="Aomi"   connectionString="Data Source=.;Initial Catalog=Northwind;User ID=sa;Password=123" providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

LinqToDB自己定義一個配置類。正如上面看到的一樣子。我們只要設置下面一段配置就可以了。相信筆者應該能看懂吧。就是指定默認的連接。而對於connectionStrings節點的使用別讓筆者費神了吧。

<linq2db defaultConfiguration="Aomi" />

配置好了上面的信息。對應的繼承DataContext類的子類就可以不必須去設置對應的連接字符串了。對應的代碼如下。

 1 using LinqToDB;
 2 using LinqToDB.DataProvider.SqlServer;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 
 8 namespace LinqToDBExample
 9 {
10     public class DbNorthwind:DataContext
11     {
12         public ITable<Products> Products { get { return this.GetTable<Products>(); } }
13 
14     }
15 }

事實上這種用配置的方法在EF里面也存在,而上面方法就是通過傳入參數來確定對應的連接字符串。只不過上面在配置文件就指定了默認的連接。所以我們可以不用指定默認的,用直接指定的方式來獲得連接。如下

 1 using LinqToDB;
 2 using LinqToDB.DataProvider.SqlServer;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 
 8 namespace LinqToDBExample
 9 {
10     public class DbNorthwind:DataContext
11     {
12         public DbNorthwind()
13             : base("Aomi")
14         { }
15         public ITable<Products> Products { get { return this.GetTable<Products>(); } }
16 
17     }
18 }

如果大家還是不滿意的話,筆者也只能跪求解脫——因為筆者只知道這三種方式了。

LinqToDB的增加功能

對於EF相信大家都知道沒有什么增加不增加的概念。至少筆者是這樣子認為的。因為筆者沒有看到對應的增加方法。很多人說難道對一個同步數據庫的集合表進行增加數據就不算增加嗎(這里集合表就是從IDbSet中得來的集合)。筆者認不是。EF好像只有數據有沒有發生變化這個概念。你對集合表操作就表現了數據發現變化。最后提交變化的時候,EF會知道原來增加了。形式上有一點像是在用DataSet進行數據庫。而LinqToDB卻不是這樣子,他還是有增加這個概念了。讓筆者舉一些列子吧。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 
 7 namespace LinqToDBExample
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             using(var db = new DbNorthwind())
14             {
15                 Products product = new Products();
16                 product.Name = "aomi";
17 
18                 db.Insert(product);
19             }
20 
21             Console.ReadKey();
22         }
23     }
24 }

執行結果:

上面做了一個簡單的操作。只是可惜LinqToDB的增加功能有一點讓筆者失望。他並沒有像EF那么樣子。增加的時候,如果主鍵是自動增加(標識),那么增加成功之后會把主鍵同步到增加對象中。如圖下,增加成之后我們看到對象ProductID還是0。

對於這一點LinqToDB到是用了一種傳統的方式。擴展了一叫InsertWithIdentity方法。增加成之后返回對象主鍵。所以相對於上面想要把增加成之后的主建同步到對應增加對象的屬性中就必須用傳統的方式。筆者真的一點受不了。修改如下。

 product.ProductID = Convert.ToInt32(db.InsertWithIdentity(product));

雖然上面的增加功能讓筆者一時難以接受。不過作者在增加方面還擴展不少方法。讓我們可以直接在對應的表屬性上面操作。即是ITable接口類型的屬性。這個時候只要設置對象屬性值就可以了。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 
 7 namespace LinqToDBExample
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             using(var db = new DbNorthwind())
14             {
15                 db.Products.Value(t => t.Name, "aomi1")
16                     .Insert();
17             }
18 
19             Console.ReadKey();
20         }
21     }
22 }

看到了吧。上面由於筆者的Products類只寫了一個屬性。ProductID屬性又是自動增加。所以只有一個Value。如果要對多個屬性設置,就可以在Value在點Value。最后在點Insert或是InsertWithIdentity。

LinqToDB的更新功能

看完了LinqToDB的增加功能之后。不知道大家對LinqToDB有沒有一種想要去了解沖動呢?沒事,讓我們看一下LinqToDB對更新方面,又做了一些什么變化。不,不能說什么變化。應該說做了什么設計。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 
 7 namespace LinqToDBExample
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             using(var db = new DbNorthwind())
14             {
15 
16                 Products product = new Products();
17                 product.ProductID = 79;
18                 product.Name = "aomi update";
19 
20                 db.Update(product);
21             
22             }
23 
24             Console.ReadKey();
25         }
26     }
27 }

上面這段代碼做的事情很簡單——更新ProductID為79的數據的Name值。執行成功並且更新數據庫。你敢信。筆者記得當場傻眼。過用EF的開發人員都知道如果我們新建一個普通的對象。注意不是從數據庫里面Linq出來的。而是用new關鍵字新建的。這時候你意圖更新但是EF卻會變成增加。可是LinqToDB卻不存在這樣子的問題。這設計筆者給99分。1分是筆者不認同——感覺設計思想有一點亂來。早的時候筆者一直希望看到LinqToDB在操作對象上能像EF或Hibernate那樣子——存在對象狀態的說法。從增加的時候主鍵不能同步到現在新建對象更新成來看LinqToDB並沒有對象持久化這個說法。也罷,必竟是一個輕量級的ORM框架。

上面的更新做法相信大家一定不會有什么煩感。如果新建的對象設置主鍵的值之后,都能更新。那么從數據庫Linq出來的數據更不用講了。主要是要看看一下LinqToDB給我們帶來另一個更新的做法。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 
 7 namespace LinqToDBExample
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             using(var db = new DbNorthwind())
14             {
15                 db.Products
16                     .Where(t => t.ProductID == 83)
17                     .Set(t => t.Name, "Update aomi")
18                     .Update();
19             }
20 
21             Console.ReadKey();
22         }
23     }
24 }

由於增加功能里面的Value靜態擴展方法出現,導至筆者對更新功能的Set靜態擴展方法出現一點也不奇怪。如果要更新多個屬性的值,只要是點多個Set就可以了。當然最后不要忘記了點Update。更新功能的擴展方法筆者還是比較喜歡的。相對於EF來講,EF必須先到數據庫獲得要更新的對象。然后在修改對象的屬性值才能更新。而對於LinqToDB來講,只要一步到位。

LinqToDB的刪除功能

看完了增加和更新,顯然不能忘了看一下LinqToDB在刪除方面是如何做的。正如上面所講的LinqToDB並沒有對象狀態這一個說法。所以下面的代碼刪除成功了也不會覺得奇怪。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 
 7 namespace LinqToDBExample
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             using(var db = new DbNorthwind())
14             {
15                 Products product = new Products();
16                 product.ProductID = 79;
17 
18                 db.Delete(product);     
19             }
20 
21             Console.ReadKey();
22         }
23     }
24 }

另一種做法:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 
 7 namespace LinqToDBExample
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             using(var db = new DbNorthwind())
14             {
15                 db.Products.Where(t => t.ProductID == 83).Delete();
16             }
17 
18             Console.ReadKey();
19         }
20     }
21 }

上面的倆種做法。筆者很淡定的飄過。不過到是說明了一個問題——增刪改一般都有倆種方式。一種是從IDataContext接口上擴展出來的方法。一種是從IQueryable接口或是ITable接口擴展出來的方法。

在開發一個大型項目的時候,對於多表操作顯得家常便飯。所以每一個框架都有自己處理事務的功能。同樣子LinqToDB也實現了事務功能。不過他的事務讓筆者很舒服,不是因為他有多么好。而是他更加接近原生態。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 using LinqToDB.Data;
 7 
 8 namespace LinqToDBExample
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             using(var db = new DbNorthwind())
15             {
16                 using (DataContextTransaction transaction = db.BeginTransaction())
17                 {
18                     try
19                     {
20                         int deleteID = Convert.ToInt32(db.Products.Value(t => t.Name, "aomi79").InsertWithIdentity());
21                         db.Products.Where(t => t.ProductID == 79).Delete();
22                     }
23                     catch (Exception ex)
24                     {
25                     }
26                 }
27             
28             }
29 
30             Console.ReadKey();
31         }
32     }
33 }

上面的做法用的是自動提交事務,如果你想要自己提交的事務的話,一定要設置他的參數。默認事務是自動提交事務的。如下,筆者關閉掉自動提交事務。變成手動提交了。所以傳入false。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using LinqToDB;
 6 using LinqToDB.Data;
 7 
 8 namespace LinqToDBExample
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             using(var db = new DbNorthwind())
15             {
16                 using (DataContextTransaction transaction = db.BeginTransaction(false))
17                 {
18                         db.Products.Value(t => t.Name, "aomi83").Insert();
19                         int deleteID = Convert.ToInt32(db.Products.Value(t => t.Name, "aomi79").InsertWithIdentity());
20                         int count = db.Products.Where(t => t.ProductID == deleteID).Delete();
21 
22                         if (count <= 0)
23                         {
24                             transaction.RollbackTransaction();
25                         }
26                         else
27                         {
28                             transaction.CommitTransaction();
29                         }
30                 }
31             
32             }
33 
34             Console.ReadKey();
35         }
36     }
37 }

 結束語


LinqToDB框架有他自己的優點和特色。同樣子也有他的不足。而筆者想為大家介紹LinqToDB的使用的同時更想為介紹大家他是什么實現的。如果本系列中有出現錯誤的說法,希望大家能諒解。可以的話,請提出來讓筆者也學習一下。謝謝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM