LinqToDB 源码分析——前言

时间:2021-01-07 16:48:07

记得笔者进入公司的时候接触的第一个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 源码分析——前言

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有一种亲近感。

 using LinqToDB;
using LinqToDB.DataProvider.SqlServer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace LinqToDBExample
{
public class DbNorthwind:DataContext
{
public DbNorthwind()
: base(SqlServerTools.GetDataProvider(SqlServerVersion.v2008), "Data Source=.;Initial Catalog=Northwind;User ID=sa;Password=123")
{ } public ITable<Products> Products { get { return this.GetTable<Products>(); } } }
}

笔者新建一个类DbNorthwind。让这个类继承DataContext类。同时增加了获得Northwind数据库中dbo.Products表对应的关系类属性Products。这个属性是一个ITable接口。当然跟EF的IDbSet接口有一曲同工之妙。是不是觉得像EF找到了失散多年的兄弟。而对映射配置上那些事情只怕看完之后,基本上你会跟笔者一样子淡定了许多。也许你很少用到EF的注解配置,但是这不能代表他们俩者之间不存在相似之处。

 using LinqToDB.Mapping;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace LinqToDBExample
{
public class Products
{
[PrimaryKey, Identity]
public int ProductID { set; get; } [Column(Name = "ProductName"), NotNull]
public string Name { get; set; }
}
}

看到了吧。可以说没有什么新的知识。即使没有用过EF,也可以从关键字中得到对应的信息。笔者也相信关键字PrimaryKey你在Sql Server中一定会有用到过。即是主键的意思。这时候来一个简单的查询应该是一件非常棒的事情。可以让我们看到他在查询是如何的表现。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
var query = from p in db.Products
where p.ProductID >
orderby p.Name descending
select p; foreach (Products product in query.ToList())
{
Console.WriteLine(string.Format("ProductID:{0} ------------ProductName:{1}", product.ProductID, product.Name));
} } Console.ReadKey();
}
}
}

执行结果:

LinqToDB 源码分析——前言

显然,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类的子类就可以不必须去设置对应的连接字符串了。对应的代码如下。

 using LinqToDB;
using LinqToDB.DataProvider.SqlServer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace LinqToDBExample
{
public class DbNorthwind:DataContext
{
public ITable<Products> Products { get { return this.GetTable<Products>(); } } }
}

事实上这种用配置的方法在EF里面也存在,而上面方法就是通过传入参数来确定对应的连接字符串。只不过上面在配置文件就指定了默认的连接。所以我们可以不用指定默认的,用直接指定的方式来获得连接。如下

 using LinqToDB;
using LinqToDB.DataProvider.SqlServer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace LinqToDBExample
{
public class DbNorthwind:DataContext
{
public DbNorthwind()
: base("Aomi")
{ }
public ITable<Products> Products { get { return this.GetTable<Products>(); } } }
}

如果大家还是不满意的话,笔者也只能跪求解脱——因为笔者只知道这三种方式了。

LinqToDB的增加功能

对于EF相信大家都知道没有什么增加不增加的概念。至少笔者是这样子认为的。因为笔者没有看到对应的增加方法。很多人说难道对一个同步数据库的集合表进行增加数据就不算增加吗(这里集合表就是从IDbSet中得来的集合)。笔者认不是。EF好像只有数据有没有发生变化这个概念。你对集合表操作就表现了数据发现变化。最后提交变化的时候,EF会知道原来增加了。形式上有一点像是在用DataSet进行数据库。而LinqToDB却不是这样子,他还是有增加这个概念了。让笔者举一些列子吧。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
Products product = new Products();
product.Name = "aomi"; db.Insert(product);
} Console.ReadKey();
}
}
}

执行结果:

LinqToDB 源码分析——前言

上面做了一个简单的操作。只是可惜LinqToDB的增加功能有一点让笔者失望。他并没有像EF那么样子。增加的时候,如果主键是自动增加(标识),那么增加成功之后会把主键同步到增加对象中。如图下,增加成之后我们看到对象ProductID还是0。

LinqToDB 源码分析——前言

对于这一点LinqToDB到是用了一种传统的方式。扩展了一叫InsertWithIdentity方法。增加成之后返回对象主键。所以相对于上面想要把增加成之后的主建同步到对应增加对象的属性中就必须用传统的方式。笔者真的一点受不了。修改如下。

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

虽然上面的增加功能让笔者一时难以接受。不过作者在增加方面还扩展不少方法。让我们可以直接在对应的表属性上面操作。即是ITable接口类型的属性。这个时候只要设置对象属性值就可以了。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
db.Products.Value(t => t.Name, "aomi1")
.Insert();
} Console.ReadKey();
}
}
}

看到了吧。上面由于笔者的Products类只写了一个属性。ProductID属性又是自动增加。所以只有一个Value。如果要对多个属性设置,就可以在Value在点Value。最后在点Insert或是InsertWithIdentity。

LinqToDB的更新功能

看完了LinqToDB的增加功能之后。不知道大家对LinqToDB有没有一种想要去了解冲动呢?没事,让我们看一下LinqToDB对更新方面,又做了一些什么变化。不,不能说什么变化。应该说做了什么设计。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{ Products product = new Products();
product.ProductID = ;
product.Name = "aomi update"; db.Update(product); } Console.ReadKey();
}
}
}

上面这段代码做的事情很简单——更新ProductID为79的数据的Name值。执行成功并且更新数据库。你敢信。笔者记得当场傻眼。过用EF的开发人员都知道如果我们新建一个普通的对象。注意不是从数据库里面Linq出来的。而是用new关键字新建的。这时候你意图更新但是EF却会变成增加。可是LinqToDB却不存在这样子的问题。这设计笔者给99分。1分是笔者不认同——感觉设计思想有一点乱来。早的时候笔者一直希望看到LinqToDB在操作对象上能像EF或Hibernate那样子——存在对象状态的说法。从增加的时候主键不能同步到现在新建对象更新成来看LinqToDB并没有对象持久化这个说法。也罢,必竟是一个轻量级的ORM框架。

上面的更新做法相信大家一定不会有什么烦感。如果新建的对象设置主键的值之后,都能更新。那么从数据库Linq出来的数据更不用讲了。主要是要看看一下LinqToDB给我们带来另一个更新的做法。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
db.Products
.Where(t => t.ProductID == )
.Set(t => t.Name, "Update aomi")
.Update();
} Console.ReadKey();
}
}
}

由于增加功能里面的Value静态扩展方法出现,导至笔者对更新功能的Set静态扩展方法出现一点也不奇怪。如果要更新多个属性的值,只要是点多个Set就可以了。当然最后不要忘记了点Update。更新功能的扩展方法笔者还是比较喜欢的。相对于EF来讲,EF必须先到数据库获得要更新的对象。然后在修改对象的属性值才能更新。而对于LinqToDB来讲,只要一步到位。

LinqToDB的删除功能

看完了增加和更新,显然不能忘了看一下LinqToDB在删除方面是如何做的。正如上面所讲的LinqToDB并没有对象状态这一个说法。所以下面的代码删除成功了也不会觉得奇怪。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
Products product = new Products();
product.ProductID = ; db.Delete(product);
} Console.ReadKey();
}
}
}

另一种做法:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
db.Products.Where(t => t.ProductID == ).Delete();
} Console.ReadKey();
}
}
}

上面的俩种做法。笔者很淡定的飘过。不过到是说明了一个问题——增删改一般都有俩种方式。一种是从IDataContext接口上扩展出来的方法。一种是从IQueryable接口或是ITable接口扩展出来的方法。

在开发一个大型项目的时候,对于多表操作显得家常便饭。所以每一个框架都有自己处理事务的功能。同样子LinqToDB也实现了事务功能。不过他的事务让笔者很舒服,不是因为他有多么好。而是他更加接近原生态。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB;
using LinqToDB.Data; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
using (DataContextTransaction transaction = db.BeginTransaction())
{
try
{
int deleteID = Convert.ToInt32(db.Products.Value(t => t.Name, "aomi79").InsertWithIdentity());
db.Products.Where(t => t.ProductID == ).Delete();
}
catch (Exception ex)
{
}
} } Console.ReadKey();
}
}
}

上面的做法用的是自动提交事务,如果你想要自己提交的事务的话,一定要设置他的参数。默认事务是自动提交事务的。如下,笔者关闭掉自动提交事务。变成手动提交了。所以传入false。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinqToDB;
using LinqToDB.Data; namespace LinqToDBExample
{
class Program
{
static void Main(string[] args)
{
using(var db = new DbNorthwind())
{
using (DataContextTransaction transaction = db.BeginTransaction(false))
{
db.Products.Value(t => t.Name, "aomi83").Insert();
int deleteID = Convert.ToInt32(db.Products.Value(t => t.Name, "aomi79").InsertWithIdentity());
int count = db.Products.Where(t => t.ProductID == deleteID).Delete(); if (count <= )
{
transaction.RollbackTransaction();
}
else
{
transaction.CommitTransaction();
}
} } Console.ReadKey();
}
}
}

结束语


LinqToDB框架有他自己的优点和特色。同样子也有他的不足。而笔者想为大家介绍LinqToDB的使用的同时更想为介绍大家他是什么实现的。如果本系列中有出现错误的说法,希望大家能谅解。可以的话,请提出来让笔者也学习一下。谢谢。