前言

时间退回到 2009-09-26,为了演示开源项目 FineUI 的使用方法,我们发布了 AppBox(通用权限管理框架,包括用户管理、职称管理、部门管理、角色管理、角色权限管理等模块),最初的 AppBox 采用 Subsonic 作为 ORM 工具。

遗憾的是,Subsonic后来逐渐不再维护,我们于 2013-08-28 正式从 Subsonic 转到 Entity Framework,最初对 Entity Framework 接触只能用两个字来形容:惊艳!整个 AppBox 项目没有写一行 SQL 代码,甚至没有打开 SQLServer 数据库,全部代码用 C# 来完成,EF CodeFirst小心翼翼的帮组我们完成了从数据库创建,访问,查询,更新,删除等一系列操作。

AppBox的详细介绍:https://www.cnblogs.com/sanshi/p/4030265.html

5 年来,我们一直在津津乐道 Entity Framework 带来的好处,也许是情人眼里出西施,对于它的缺点文过饰非,大可用一句话搪塞:你要完整学习 Entity Framework 知识体系,方能事半功倍,俗话说:磨刀不误砍柴工。

一般来说,新手的问题无外乎如下几点:

1. 数据库在哪?怎么没有数据库初始脚本?

2. 怎么又出错了?到底执行的SQL语句是啥?

3. 怎么支持 MySQL 数据库?为什么SQLServer正常的查询,到MySQL就出错了?

4. 为啥突然数据库都清空了?好恐怖,幸好不是在服务器

5. 性能怎么样?大家都说EF的性能不好

6. 能不能先建数据库,然后生成模型类?

.....

这些问题,有些是可以解决的,有些是对EF不了解遇到的,有些的确是EF自身的问题。

比如对 MYSQL 的支持不好,这个问题在简单的查询时正常,一遇到复杂的查询,总会遇到各种问题。而数据库被清空那个则是不了解EF的 Data Migration机制。性能倒不是大问题,只要合理的查询,加上EF的持续优化,性能应该还是可预期的。

即使一切的问题都可以归纳到没有好好学学,那 Entity Framework 总归还是有一个大问题:入门容易,而知识体系有点复杂,学习曲线会比较陡峭!

为什么要转到Dapper?

如果你认为上面就是我们转到 Dapper 的原因,那你算错了。5年的时间,我们已经对 Entity Framework 有了足够的了解和掌握,因此上面的问题都已不是问题。真正出现问题的不是 Entity Framework,而是我们,好吧,就明说了吧:我们太想念 SQL 语句了!

Entity Framework是一个有益的尝试,尝试向开发人员隐藏 SQL 语句,所有的数据库查询操作都通过面向对象的 C# 语言来完成,可以想象,从关系型数据库抽象为面向对象的语言,这个扭曲力场不可谓不强大,而这个扭曲力会带来两个极端:

1. 简单的操作会更加简单

2. 复杂的操作会更加复杂

哪些是简单的操作呢?

比如创建数据库:

Entity Framework CodeFirst开发模式允许我们只写模型类,程序会在第一次运行时创建数据库,比如一个简单的用户角色关系,通过模型类可以这么定义:

public class Role : IKeyID
{
[Key]
public int ID { get; set; } [Required, StringLength()]
public string Name { get; set; } [StringLength()]
public string Remark { get; set; } public virtual ICollection<User> Users { get; set; } }
public class User : IKeyID
{
[Key]
public int ID { get; set; } [Required, StringLength()]
public string Name { get; set; } [Required, StringLength()]
public string Email { get; set; } [Required, StringLength()]
public string Password { get; set; } public virtual ICollection<Role> Roles { get; set; } }

然后通过C#代码定义模型关联:

modelBuilder.Entity<Role>()
.HasMany(r => r.Users)
.WithMany(u => u.Roles)
.Map(x => x.ToTable("RoleUsers")
.MapLeftKey("RoleID")
.MapRightKey("UserID"));

这里是意思是:

1. 一个角色可以有多个用户(HasMany)

2. 一个用户可以有多个角色(WithMany)

3. 将这种关联关系保存到数据库表 RoleUsers,对于两个外键:RoleID和UserID

上面的代码如果在MySQL数据库中直接创建,熟悉SQL语句的会感觉更加亲切:

CREATE TABLE IF NOT EXISTS `roles` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(50) CHARACTER NOT NULL,
`Remark` varchar(500) CHARACTER DEFAULT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
); CREATE TABLE IF NOT EXISTS `users` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(50) CHARACTER NOT NULL,
`Email` varchar(100) CHARACTER NOT NULL,
`Password` varchar(50) CHARACTER NOT NULL,
`Enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `ID` (`ID`)
); CREATE TABLE IF NOT EXISTS `roleusers` (
`RoleID` int(11) NOT NULL,
`UserID` int(11) NOT NULL,
PRIMARY KEY (`RoleID`,`UserID`),
KEY `Role_Users_Target` (`UserID`),
CONSTRAINT `Role_Users_Source` FOREIGN KEY (`RoleID`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
CONSTRAINT `Role_Users_Target` FOREIGN KEY (`UserID`) REFERENCES `users` (`id`) ON DELETE CASCADE
);

在表 roleusers 中,创建了两个约束,分别是:

1. Role_Users_Source:定义外键 RoleID,关联 roles 表的 ID 列,并使用 ON DELETE CASCADE 定义级联删除,如果roles 表删除了一行数据,那么roleusers 中一行或多行关联数据会被删除

2. Role_Users_Target:定义外键 UserID,关联 users 表的 ID 列,同样定义级联删除规则

再比如简单的CRUD操作:

获取指定ID的角色:

DB.Roles.Find(id)

更新某个角色:

Role item = DB.Roles.Find(id);
item.Name = tbxName.Text.Trim();
item.Remark = tbxRemark.Text.Trim();
DB.SaveChanges();

删除某个角色:

DB.Roles.Where(r => r.ID == roleID).Delete();

获取某个角色下的用户数:

DB.Users.Where(u => u.Roles.Any(r => r.ID == roleID)).Count();

这个C#代码虽然看着简单,不是 Entity Framework 生成的SQL语句看起来却不是很友好:

SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
COUNT(1) AS [A1]
FROM [dbo].[Users] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[RoleUsers] AS [Extent2]
WHERE ([Extent1].[ID] = [Extent2].[UserID]) AND ([Extent2].[RoleID] = @p__linq__0)
)
) AS [GroupBy1]

可能是考虑到 C# 代码可能会比较复杂,从通用性的角度出发,EF为一个简单的查询生成了包含 3 个 SELECT 的 SQL 查询语句。

如果仔细观察上面的SQL代码,有效的只是如下部分:

SELECT
COUNT(1)
FROM [dbo].[Users]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[RoleUsers]
WHERE ([Users].[ID] = [RoleUsers].[UserID]) AND ([RoleUsers].[RoleID] = @p__linq__0)
)

而这个SQL的外层SELECT其实是多余的,简化后的SQL代码是这样的:

SELECT
COUNT(*)
FROM [dbo].[RoleUsers]
WHERE ([Users].[ID] = [RoleUsers].[UserID]) AND ([RoleUsers].[RoleID] = @p__linq__0)

可见,为了完成需要的操作,Entity Framework为我们封装了多余的SQL代码,这让我们有点担心,且不说多余的两个SELECT会不会对性能有印象(这里可能没有,复杂的情况就不一定了),EF总给人一种雾里看花的感觉,因为最终还是要落实到SQL语句上来。

完成同样的操作,用 Dapper 可能要稍微多写点代码,但是 SQL 语句让人看着心里更有谱:

获取指定ID的角色:

conn.QuerySingleOrDefault<Role>("select * from roles where ID = @RoleID", new { RoleID = roleID });

更新某个角色:

Role item = GetCurrentRole(id);
item.Name = tbxName.Text.Trim();
item.Remark = tbxRemark.Text.Trim(); conn.Execute("update roles set Name = @Name, Remark = @Remark where ID = @ID", item);

删除某个角色:

conn.Execute("delete from roles where ID = @RoleID", new { RoleID = roleID });

获取某个角色下的用户数:

conn.QuerySingle<int>("select count(*) from roleusers where RoleID = @RoleID", new { RoleID = roleID });

哪些是复杂的操作呢?

因为数据库是关系型,Entity Framework偏偏要用面向对象的 C# 来操作,遇到级联关系的更新时,EF就会变得有点复杂。

比如从某个角色中删除多个用户:

在 Entity Framework中,我们需要先获取这个角色以及属于这个角色的用户,然后才能执行删除操作。

int roleID = GetSelectedDataKeyID(Grid1);
List<int> userIDs = GetSelectedDataKeyIDs(Grid2); Role role = DB.Roles.Include(r => r.Users)
.Where(r => r.ID == roleID)
.FirstOrDefault(); foreach (int userID in userIDs)
{
User user = role.Users.Where(u => u.ID == userID).FirstOrDefault();
if (user != null)
{
role.Users.Remove(user);
}
} DB.SaveChanges();

从代码逻辑上讲,这个代码片段是很直观的:

1. 首先获取当前角色,由于后面要操作角色的用户列表,所以使用 Include 语句,这将导致生成SQL查询语句有点复杂:

SELECT
[Project2].[ID] AS [ID],
[Project2].[Name] AS [Name],
[Project2].[Remark] AS [Remark],
[Project2].[C1] AS [C1],
[Project2].[ID1] AS [ID1],
[Project2].[Name1] AS [Name1],
FROM ( SELECT
[Limit1].[ID] AS [ID],
[Limit1].[Name] AS [Name],
[Limit1].[Remark] AS [Remark],
[Join1].[ID] AS [ID1],
[Join1].[Name] AS [Name1],
CASE WHEN ([Join1].[RoleID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM (SELECT TOP (1)
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Remark] AS [Remark]
FROM [dbo].[Roles] AS [Extent1]
WHERE [Extent1].[ID] = @p__linq__0 ) AS [Limit1]
LEFT OUTER JOIN (SELECT [Extent2].[RoleID] AS [RoleID], [Extent3].[ID] AS [ID], [Extent3].[Name] AS [Name]
FROM [dbo].[RoleUsers] AS [Extent2]
INNER JOIN [dbo].[Users] AS [Extent3] ON [Extent3].[ID] = [Extent2].[UserID] ) AS [Join1] ON [Limit1].[ID] = [Join1].[RoleID]
) AS [Project2]
ORDER BY [Project2].[ID] ASC, [Project2].[C1] ASC

2. 遍历需要删除的用户列表,并从当前角色的用户列表中删除,这将执行多个SQL语句:

exec sp_executesql N'DELETE [dbo].[RoleUsers]
WHERE (([RoleID] = @0) AND ([UserID] = @1))',N'@0 int,@1 int',@0=3,@1=45
go
exec sp_executesql N'DELETE [dbo].[RoleUsers]
WHERE (([RoleID] = @0) AND ([UserID] = @1))',N'@0 int,@1 int',@0=3,@1=46
go
exec sp_executesql N'DELETE [dbo].[RoleUsers]
WHERE (([RoleID] = @0) AND ([UserID] = @1))',N'@0 int,@1 int',@0=3,@1=47
go

。。。。。

上面的C#代码以及生成的SQL语句之所以这么复杂,归根到底是因为 Entity Framework 企图使用面向对象的方式操作关系型数据库,换句话说:模型类对数据库的 RoleUsers 表是一无所知的。

而使用 Dapper 代码,代码非常简单,因为我们可以直接操作 roleusers 表:

int roleID = GetSelectedDataKeyID(Grid1);
List<int> userIDs = GetSelectedDataKeyIDs(Grid2); conn.Execute("delete from roleusers where RoleID = @RoleID and UserID in @UserIDs", new { RoleID = roleID, UserIDs = userIDs });

再比如更新某个用户的角色列表:

在 Entity Framework中,我们需要先获取这个用户以及属于这个用户的角色,然后才能执行替换操作。

User item = DB.Users
.Include(u => u.Roles)
.Where(u => u.ID == id).FirstOrDefault(); int[] roleIDs = StringUtil.GetIntArrayFromString(hfSelectedRole.Text);
ReplaceEntities<Role>(item.Roles, roleIDs); DB.SaveChanges();

而 ReplaceEntities 是我们自定义的一个帮助函数:

protected void ReplaceEntities<T>(ICollection<T> existEntities, int[] newEntityIDs) where T : class,  IKeyID, new()
{
if (newEntityIDs.Length == )
{
existEntities.Clear();
}
else
{
int[] tobeAdded = newEntityIDs.Except(existEntities.Select(x => x.ID)).ToArray();
int[] tobeRemoved = existEntities.Select(x => x.ID).Except(newEntityIDs).ToArray(); AddEntities<T>(existEntities, tobeAdded); existEntities.Where(x => tobeRemoved.Contains(x.ID)).ToList().ForEach(e => existEntities.Remove(e));
}
}

由于 Entity Framework 明确知道了删除哪些角色,以及添加哪些角色,所以会生成多条插入删除SQL语句,类似:

exec sp_executesql N'DELETE [dbo].[RoleUsers]
WHERE (([RoleID] = @) AND ([UserID] = @))',N'@ int,@ int',@0=3,@1=50
go
exec sp_executesql N'DELETE [dbo].[RoleUsers]
WHERE (([RoleID] = @) AND ([UserID] = @))',N'@ int,@ int',@0=23,@1=50
go
exec sp_executesql N'DELETE [dbo].[RoleUsers]
WHERE (([RoleID] = @) AND ([UserID] = @))',N'@ int,@ int',@0=33,@1=50
go
exec sp_executesql N'INSERT [dbo].[RoleUsers]([RoleID], [UserID])
VALUES (@, @)
',N'@ int,@ int',@0=4,@1=50
go
exec sp_executesql N'INSERT [dbo].[RoleUsers]([RoleID], [UserID])
VALUES (@, @)
',N'@ int,@ int',@0=6,@1=50
go
exec sp_executesql N'INSERT [dbo].[RoleUsers]([RoleID], [UserID])
VALUES (@, @)
',N'@ int,@ int',@0=7,@1=50
go

。。。。。。

而使用Dapper更加简单,我们无需知道此用户有哪些角色,可以直接操作 roleusers 数据库:

User item = DB.Users
.Include(u => u.Roles)
.Where(u => u.ID == id).FirstOrDefault(); int[] roleIDs = StringUtil.GetIntArrayFromString(hfSelectedRole.Text); conn.Execute("delete from roleusers where UserID = @UserID", new { UserID = userID });
conn.Execute("insert roleusers (UserID, RoleID) values (@UserID, @RoleID)", roleIDs.Select(u => new { UserID = userID, RoleID = u }).ToList());

这里的操作更加简单粗暴,一把删除用户的所有角色,然后再全部添加进去。

小结

从 Entity Framework 转到 Dapper,无关语言,无关性能,无关偏见。只因为心中对 SQL 语句的思念,对确定性和可掌握性的追求,当然也是为了更多代码量的简洁,多数据库的平等支持,以及未来更多调优的可能。

不可否认,Entity Framework作为一个极致(Duan)的封装,有他的受众和优点。但是,我更喜欢 Dapper 的简洁和 SQL 语句的确定性。

后记

1. 文中提到的 AppBox 不是免费软件,如果需要了解更多详情,请加入【三石和他的朋友们】知识星球下载源代码:http://fineui.com/fans/

2. 取决于本篇博文的受欢迎程度,我可能会写一个续篇,包含更多的升级细节和Dapper的使用技巧:

  • 批量更新数据
  • 分页与排序的简单封装
  • 插入与更新的简单封装
  • 事务(Transaction)
  • 插入后返回自增ID
  • 动态创建匿名参数
  • 子查询
  • 多结果映射

最后,放几张系统的截图:

【续】5年后,我们为什么要从 Entity Framework 转到 Dapper 工具?

5年后,我们为什么要从 Entity Framework 转到 Dapper 工具?的更多相关文章

  1. 【续】5年后,我们为什么要从 Entity Framework 转到 Dapper 工具?

    前言 上一篇文章收获了 140 多条评论,这是我们始料未及的. 向来有争议的话题都是公说公的理,婆说婆的理,Entity Framework的爱好者对此可以说是嗤之以鼻,不屑一顾,而Dapper爱好者 ...

  2. Entity Framework Code First学习系列目录

    Entity Framework Code First学习系列说明:开发环境为Visual Studio 2010 + Entity Framework 5.0+MS SQL Server 2012, ...

  3. Entity Framework Code First数据库连接

    1. 安装Entity Framework 使用NuGet安装Entity Framework程序包:工具->库程序包管理器->程序包管理器控制台,执行以下语句: PM> Insta ...

  4. 关于Entity Framework使用的简单例子

    一.创建Code First模型 1.创建工程,这里我使用的是以.NET 4.0为目标的实体Web应用程序 2.安装Entity Framework 确保已安装NuGet,选择NuGet套件管理员&g ...

  5. Entity Framework Code First学习系列

    Entity Framework Code First学习系列目录 Entity Framework Code First学习系列说明:开发环境为Visual Studio 2010 + Entity ...

  6. 【转】MVC Model建模及Entity Framework Power Tool使用

    MVC如使用Code-First代码优先约定,先建实体类,再根据实体类创建数据库. 在创建实体类后,新建一个数据上下文类,如下: publicclassMusicStoreDB : DbContext ...

  7. vs2012配置使用entity framework 6

    项目中使用mysql作为数据库,想快速地实现一些数据服务,为了节省开发时间,提升开发效率,性能不是考虑的重点,所以选择了使用ORM框架:Entity Framework.指定了DB的table des ...

  8. Entity Framework Code First数据库连接 转载 https://www.cnblogs.com/libingql/p/3351275.html

    Entity Framework Code First数据库连接   1. 安装Entity Framework 使用NuGet安装Entity Framework程序包:工具->库程序包管理器 ...

  9. 使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First

    [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费时间往下看了. 记得09年第一次接触ORM————Linq2Sql,从此对她的爱便一发不可收拾,一年后 ...

随机推荐

  1. gulp源码解析(一)—— Stream详解

    作为前端,我们常常会和 Stream 有着频繁的接触.比如使用 gulp 对项目进行构建的时候,我们会使用 gulp.src 接口将匹配到的文件转为 stream(流)的形式,再通过 .pipe() ...

  2. 【深度学习篇】--Seq2Seq模型从初识到应用

    一.前述 架构: 问题: 1.压缩会损失信息 2.长度会影响准确率 解决办法: Attention机制:聚焦模式 “高分辨率”聚焦在图片的某个特定区域并以“低分辨率”,感知图像的周边区域的模式.通过大 ...

  3. Kubernetes的污点和容忍(下篇)

    背景 继上一篇<Kubernetes的污点和容忍(上篇)>,这是https://kubernetes.io/docs/concepts/configuration/taint-and-to ...

  4. 《HelloGitHub》第 31 期

    公告 网站新增了 Web 服务器排行榜.数据库排行榜 <HelloGitHub>第 31 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣 ...

  5. JavaScript一看就懂(3)数组

    定义数组 var a = [1, 2, 3]; typeof a; //"object", 数组是对象 a.length; //数组长度 相关操作 a[0]; //下标访问 a.p ...

  6. Python:黑板课爬虫闯关第四关

    第四关地址:http://www.heibanke.com/lesson/crawler_ex03/ 一开始看到的时候有点蒙,不知道啥意思,说密码需要找出来但也没说怎么找啊. 别急,随便输了个昵称和密 ...

  7. 并发系列(3)之 CLH、MCS 队列锁简介

    这篇博客主要是作为 AbstractQueuedSynchronizer 的背景知识介绍:平时接触也非常的少,如果你不感兴趣可以跳过:但是了解一下能更加的清楚 AQS 的设计思路: 一.自旋锁简介 通 ...

  8. SLAM+语音机器人DIY系列:(二)ROS入门——3.在ubuntu16.04中安装ROS kinetic

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...

  9. SLAM+语音机器人DIY系列:(四)差分底盘设计——1.stm32主控硬件设计

    摘要 运动底盘是移动机器人的重要组成部分,不像激光雷达.IMU.麦克风.音响.摄像头这些通用部件可以直接买到,很难买到通用的底盘.一方面是因为底盘的尺寸结构和参数是要与具体机器人匹配的:另一方面是因为 ...

  10. Lumen框架-错误&日志

    介绍 当你开始一个新的Lumen项目的时候,错误和异常功能,已经在框架中注入了.此外,Lumen还集成了Monolog日志函数,支持和提供多种强大的日志处理功能. 配置 错误详情 大量的错误信息在你的 ...