AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式
AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理、职称管理、部门管理、角色管理、角色权限管理等模块。
从Subsonic到Entity Framework
Subsonic最早发布于2008年,当时他的无代码生成模式吸引了很多人的眼球,ActiveRecord模式的支持也是Subsonic迅速流行的原因之一。Subsonic也曾经一度被认为是NHibernate的有力竞争对手。可惜在2009年左右Subsonic的作者Rob Conery被微软挖去做Asp.net MVC之后,Subsonic实际上已经死去,虽然后来Subsonic 3.0的CodingHorror也试图东山再起,但还是由于性能原因以及各个竞争对手的冲击而逐渐没落。
不过高手的确是高手,Rob Conery在2011年发表的一篇文章《Massive: 400 Lines of Data Access Happiness》出其不意地掀起了一阵Micro-ORM的热潮,随后出现了更多的微型ORM框架,比较著名的有PetaPoco,Dapper,ServiceStack.OrmLite,Simple.Data。我也曾经试用过ServiceStack.OrmLite,对他的易用性赞不绝口,特别是对其通过代码完全控制数据库的创建和操作的方式印象深刻,如下所示。
class Note
{
[AutoIncrement] // Creates Auto primary key
public int Id { get; set; } public string NoteText { get; set; }
public DateTime? LastUpdated { get; set; }
} static void Main(string[] args)
{
//Using Sqlite DB
var dbFactory = new OrmLiteConnectionFactory(
SqliteFileDb, false, SqliteDialect.Provider); using (var db = dbFactory.Open()) { db.CreateTableIfNotExists<Note>(); // Insert
db.Insert(
new Note {
SchemaUri = "tcm:0-0-0",
NoteText = "Hello world 5",
LastUpdated = new DateTime(2013, 1, 5)
}); // Read
var notes = db.Where<Note>(new { SchemaUri = "tcm:0-0-0" });
foreach (Note note in notes)
{
Console.WriteLine("note id=" + note.Id + "noteText=" + note.NoteText);
}
}
Console.ReadLine();
}
注:上面示例代码来自博客。
但最终还是因为ServiceStack.OrmLite相关资料太少,对关联表的支持不够而放弃。
===================
题外话:我非常欣赏ServiceStack.OrmLite的地方还有他对类和表的处理方式,将复杂类型按照 JSV 的格式存储在一个文本字段中。
JSV Format (i.e. JSON-like Separated Values) is a JSON inspired format that uses CSV-style escaping for the least overhead and optimal performance.
JSV:类似JSON,但是采用的是CSV风格。这样做不仅可以减少存储空间,而且加快了读取和写入速度(官方声称JSV的读写速度是JSON读写速度的 5.3 倍)。
===================
其实ServiceStack.OrmLite的代码和Entity Framework的Code First代码非常类似,AppBox之所以最终采用Entity Framework的Code First,除了官方支持、资料多(这一点非常重要,方便遇到问题时解决)外,最重要的是简洁易懂,这也是FineUI所追求的目标。所以使用FineUI做前端展现,EntityFramework(CodeFirst)做后端数据操作,简直就是绝配。
Entity Framework官方资料:http://msdn.microsoft.com/en-us/data/ee712907
Entity Framework遇到问题时搜索:http://stackoverflow.com/questions/tagged/entity-framework
使用Subsonic和Entity Framework的代码对比
Entity Framework不仅减少了代码量,而且结构更加清晰,下面对加载单个用户数据的代码进行简单的对比。
Subsonic:
int id = GetQueryIntValue("id");
XUser current = XUser.FetchByID(id);
if (current == null)
{
// 参数错误,首先弹出Alert对话框然后关闭弹出窗口
Alert.Show("参数错误!", String.Empty, ActiveWindow.GetHideReference());
return;
}
labName.Text = current.Name;
labRealName.Text = current.ChineseName;
labEmail.Text = current.CompanyEmail;
labPersonalEmail.Text = current.PersonalEmail;
labCellPhone.Text = current.CellPhone;
labOfficePhone.Text = current.OfficePhone;
labOfficePhoneExt.Text = current.OfficePhoneExt;
labHomePhone.Text = current.HomePhone;
labRemark.Text = current.Remark;
labEnabled.Text = current.Enabled ? "启用" : "禁用";
labGender.Text = current.Gender;
// 表关联查询用户所属的角色列表
XRoleCollection roles = new Select().From(XRole.Schema)
.InnerJoin(XRoleUser.RoleIdColumn, XRole.IdColumn)
.Where(XRoleUser.UserIdColumn).IsEqualTo(current.Id)
.ExecuteAsCollection<XRoleCollection>();
StringBuilder sb = new StringBuilder();
foreach (XRole role in roles)
{
sb.AppendFormat("{0},", role.Name);
}
labRole.Text = sb.ToString().TrimEnd(',');
// 初始化职称列表的选择项
XJobTitleCollection jobs = new Select().From(XJobTitle.Schema)
.InnerJoin(XJobTitleUser.JobTitleIdColumn, XJobTitle.IdColumn)
.Where(XJobTitleUser.UserIdColumn).IsEqualTo(current.Id)
.ExecuteAsCollection<XJobTitleCollection>();
sb = new StringBuilder();
foreach (XJobTitle job in jobs)
{
sb.AppendFormat("{0},", job.Name);
}
labJobTitle.Text = sb.ToString().TrimEnd(',');
// 所属部门
// 初始化角色复选框列表的选择项
XDeptCollection depts = new Select().From(XDept.Schema)
.InnerJoin(XDeptUser.DeptIdColumn, XDept.IdColumn)
.Where(XDeptUser.UserIdColumn).IsEqualTo(current.Id)
.ExecuteAsCollection<XDeptCollection>();
if (depts.Count > 0)
{
labDept.Text = depts[0].Name;
}
Entity Framework:
int id = GetQueryIntValue("id");
User current = DB.Users
.Include(u => u.Roles)
.Include(u => u.Dept)
.Include(u => u.Titles)
.Where(u => u.UserID == id).FirstOrDefault();
if (current == null)
{
// 参数错误,首先弹出Alert对话框然后关闭弹出窗口
Alert.Show("参数错误!", String.Empty, ActiveWindow.GetHideReference());
return;
}
labName.Text = current.Name;
labRealName.Text = current.ChineseName;
labCompanyEmail.Text = current.CompanyEmail;
labEmail.Text = current.Email;
labCellPhone.Text = current.CellPhone;
labOfficePhone.Text = current.OfficePhone;
labOfficePhoneExt.Text = current.OfficePhoneExt;
labHomePhone.Text = current.HomePhone;
labRemark.Text = current.Remark;
labEnabled.Text = current.Enabled ? "启用" : "禁用";
labGender.Text = current.Gender;
// 用户所属角色
labRole.Text = String.Join(",", current.Roles.Select(r => r.Name).ToArray());
// 用户的职称列表
labTitle.Text = String.Join(",", current.Titles.Select(t => t.Name).ToArray());
// 用户所属的部门
if (current.Dept != null)
{
labDept.Text = current.Dept.Name;
}
对比:
使用Subsonic加载单个用户的数据需要进行 4 次数据库查询,总代码量达到 61 行。
使用Entity Framework加载单个用户的数据需要进行 1 次数据库查询,总代码量减少为 36 行,并且结构更加清晰易懂,是不是很心动。
使用Entity Framework的准备工作
1. 使用Visual Studio 2012
虽说Visual Studio 2012不是必须的,你完全可以在VS2010中完成全部编码工作。但是VS2012包含LocalDB数据库,并且所有的官方示例都是基于VS2012的,所以使用VS2012能够帮助新手快速入门。
并且VS2012的界面真的很漂亮,灰白色的背景,蓝底色的重点关注区域,可以引导我们的注意力到最需要关注的地方。

2. 使用NuGet安装EntityFramework
在VS的工具 -> 库程序包管理器 -> 管理解决方案的NuGet程序包,搜索Entity Framework并安装,如下图所示。

编写Code First所需的模型类(Model)
这里就以用户角色为例,首先定义角色的模型类。
public class Role
{
[Key]
public int ID { get; set; } [Required, StringLength(50)]
public string Name { get; set; } [StringLength(500)]
public string Remark { get; set; } public virtual ICollection<User> Users { get; set; } }
然后是用户的模型类:
public class User
{
[Key]
public int ID { get; set; } [Required, StringLength(50)]
public string Name { get; set; } [Required, StringLength(100)]
public string Email { get; set; } [Required, StringLength(50)]
public string Password { get; set; } [Required]
public bool Enabled { get; set; } [StringLength(10)]
public string Gender { get; set; } [StringLength(100)]
public string ChineseName { get; set; } [StringLength(100)]
public string EnglishName { get; set; } [StringLength(200)]
public string Photo { get; set; } [StringLength(50)]
public string QQ { get; set; } [StringLength(100)]
public string CompanyEmail { get; set; } [StringLength(50)]
public string OfficePhone { get; set; } [StringLength(50)]
public string OfficePhoneExt { get; set; } [StringLength(50)]
public string HomePhone { get; set; } [StringLength(50)]
public string CellPhone { get; set; } [StringLength(500)]
public string Address { get; set; } [StringLength(500)]
public string Remark { get; set; } [StringLength(50)]
public string IdentityCard { get; set; } public DateTime? Birthday { get; set; }
public DateTime? TakeOfficeTime { get; set; }
public DateTime? LastLoginTime { get; set; }
public DateTime? CreateTime { get; set; } public virtual ICollection<Role> Roles { get; set; } }
注意,我们在此定义了两个导航属性(Navigation Property),分别是 Role.Users 和 User.Roles,并且声明为 virtual ,其实这就启用了Entity Framework的延迟加载特性。在后面的代码中,你会看到我们都是使用 Include 来即时加载数据(内部SQL实现是表关联),从而避免了延迟加载造成的多次数据库连接。
在上面定义中,我们使用了一些Data Annotations来声明属性,比如Key用来跟踪每一个模型类的实例(也就是实体 - Entity,这也许就是Entity Framework名字的由来),对应到数据库表中的主键。StringLength则用来定义属性的长度,对应到数据库表中字段的长度。更多的Data Annotations请参考:http://msdn.microsoft.com/en-us/data/jj591583
使用Fluent API来配置模型类的关系
虽然使用Data Annotation也能设定模型类的关系,但是不够灵活。Entity Framework还提供了另一种方式Fluent API来设置关系,详细的介绍可以参考博客园 dudu 老大的这篇文章:http://www.cnblogs.com/dudu/archive/2011/07/11/ef_one-to-one_one-to-many_many-to-many.html
定义用户和角色之间多对多的关系:
public class AppBoxContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); modelBuilder.Entity<Role>()
.HasMany(r => r.Users)
.WithMany(u => u.Roles)
.Map(x => x.ToTable("RoleUsers")
.MapLeftKey("RoleID")
.MapRightKey("UserID")); }
}
用更加通俗的话来解释上面的代码:
1. 一个角色(Role)有很多(HasMany)用户(Users);
2. 每个用户(Users)又有很多(WithMany)角色(Roles);
3. 把这种多对多的关系映射到一张表(RoleUsers),外键分别是RoleID和UserID。
需要注意的是,在Entity Framework不能直接对关联表进行操作,需要通过Role或者User实体来修改添加删除关系。
编写数据库初始化代码
1. 首先在Global.asax中设置数据库初始化类:
protected void Application_Start(object sender, EventArgs e)
{
Database.SetInitializer(new AppBoxDatabaseInitializer()); }
2. 定义数据库初始化类:
public class AppBoxDatabaseInitializer : DropCreateDatabaseIfModelChanges<AppBoxContext> // DropCreateDatabaseAlways<AppBoxContext>
{
protected override void Seed(AppBoxContext context)
{
GetUsers().ForEach(u => context.Users.Add(u));
GetRoles().ForEach(r => context.Roles.Add(r));
} private static List<Role> GetRoles()
{
var roles = new List<Role>()
{
new Role()
{
Name = "系统管理员",
Remark = ""
},
new Role()
{
Name = "部门管理员",
Remark = ""
},
new Role()
{
Name = "项目经理",
Remark = ""
},
new Role()
{
Name = "开发经理",
Remark = ""
},
new Role()
{
Name = "开发人员",
Remark = ""
},
new Role()
{
Name = "后勤人员",
Remark = ""
},
new Role()
{
Name = "外包人员",
Remark = ""
}
}; return roles;
} private static List<User> GetUsers()
{
string[] USER_NAMES = { "男", "童光喜", "男", "方原柏", "女", "祝春亚", "男", "涂辉", "男", "舒兆国" };
string[] EMAIL_NAMES = { "qq.com", "gmail.com", "163.com", "126.com", "outlook.com", "foxmail.com" }; var users = new List<User>();
var rdm = new Random(); for (int i = 0, count = USER_NAMES.Length; i < count; i += 2)
{
string gender = USER_NAMES[i];
string chineseName = USER_NAMES[i + 1];
string userName = "user" + i.ToString(); users.Add(new User
{
Name = userName,
Gender = gender,
Password = PasswordUtil.CreateDbPassword(userName),
ChineseName = chineseName,
Email = userName + "@" + EMAIL_NAMES[rdm.Next(0, EMAIL_NAMES.Length)],
Enabled = true,
CreateTime = DateTime.Now
});
} // 添加超级管理员
users.Add(new User
{
Name = "admin",
Gender = "男",
Password = PasswordUtil.CreateDbPassword("admin"),
ChineseName = "超级管理员",
Email = "admin@examples.com",
Enabled = true,
CreateTime = DateTime.Now
}); return users;
}
}
开始查询数据库
使用如下代码查询单个用户:
using(var db = new AppBoxContext())
{
int id = Convert.ToInt32(Request.QueryString["id"]);
User current = db.Users
.Include(u => u.Roles)
.Where(u => u.UserID == id).FirstOrDefault();
if (current != null)
{
labName.Text = current.Name;
labRealName.Text = current.ChineseName;
labGender.Text = current.Gender; // 用户所属角色
labRole.Text = String.Join(",", current.Roles.Select(r => r.Name).ToArray());
}
}
但是每次都写using 会觉得很烦,能不能就将AppBoxContext实例存储在一个变量中呢,下面这篇文章给出了最佳实践:
One DbContext per Request
我们的实现,在Global.asax的后台代码中:
protected void Application_BeginRequest(object sender, EventArgs e)
{ } protected virtual void Application_EndRequest()
{
var context = HttpContext.Current.Items["__AppBoxContext"] as AppBoxContext;
if (context != null)
{
context.Dispose();
}
}
然后在PageBase基类中:
public static AppBoxContext DB
{
get
{
// http://stackoverflow.com/questions/6334592/one-dbcontext-per-request-in-asp-net-mvc-without-ioc-container
if (!HttpContext.Current.Items.Contains("__AppBoxContext"))
{
HttpContext.Current.Items["__AppBoxContext"] = new AppBoxContext();
}
return HttpContext.Current.Items["__AppBoxContext"] as AppBoxContext;
}
}
下载或捐赠AppBox
1. AppBox v2.0 是免费软件,免费提供下载:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3788
2. AppBox v3.0 是捐赠软件,你可以通过捐赠作者来获取AppBox v3.0的全部源代码(http://fineui.com/donate/)。
喜欢这篇文章,请帮忙点击页面右下角的【推荐】按钮。
AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式的更多相关文章
- Entity Framework:三种开发模式实现数据访问
原文地址 http://blog.csdn.net/syaguang2006/article/details/19606715 前言 Entity Framework支持Database First. ...
- AppBox升级进行时 - 扁平化的权限设计
AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. AppBox v2.0中的权限实现 AppBox v2.0中权限管理中涉及三个 ...
- 【极力分享】[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例【转载自https://segmentfault.com/a/1190000004152660】
[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例 本文我们来学习一下在Entity Framework中使用Cont ...
- Entity Framework 之 Code First
使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费 ...
- 在Entity Framework 中用 Code First 创建新的数据库
在Entity Framework 中用 Code First 创建新的数据库 (原文链接) 本文将逐步介绍怎样用Code First 创建新数据库,使用在代码中定义类和API中提供的特性(Attri ...
- Entity Framework 6 Code First新特性:支持存储过程
Entity Framework 6提供支持存储过程的新特性,本文具体演示Entity Framework 6 Code First的存储过程操作. Code First的插入/修改/删除存储过程 默 ...
- 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表
创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...
- MVC2、MVC3、MVC4、MVC5之间的区别 以及Entity Framework 6 Code First using MVC 5官方介绍教程
现在MVC的技术日趋成熟,面对着不同版本的MVC大家不免有所迷惑 -- 它们之间有什么不同呢?下面我把我搜集的信息汇总一下,以便大家能更好的认识不同版本MVC的功能,也便于自己查阅. View Eng ...
- Entity Framework Core Code First 项目实践
Entity Framework Core Code First 实践 任何一种技术的出现都是为了解决一系列特定的问题,只有了解了技术所要解决的关键问题,才能理解它的真正用途,之后,才能在实践中用好它 ...
随机推荐
- MFC--响应鼠标和键盘操作
一个程序最重要的部分之一是对鼠标和键盘操作的响应. 一. 理解鼠标事件.之前对鼠标事件的认识仅仅局限于处理控件的单击与双击事件.但实际鼠标的操作包含很多.这里将以一个画图的小程序讲解对鼠标的响应. ...
- Python开发【第一篇】:初识Python
初识python 一.python简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解 ...
- 基本排序算法——插入排序java实现
插入排序过程: 在初始状态下,第一个元素是排序的,在最终状态下,作为一组数据时排序的. 代码如下;eclipse4.3实现 package sort.basic; import java.util.A ...
- GoogleMap和高德地图最新的瓦片图地址是用什么加密或者压缩
https://mts1.googleapis.com/vt?pb=!1m4!1m3!1i13!2i2475!3i3029!2m3!1e0!2sm!3i293208756!3m9!2sen-US!3s ...
- SharePoint 2013 Ajax 造成页面无法编辑
1.如下图,在编辑页面的时候,出现如下错误“此网页自上次打开后已被修改,必须再次打开该网页”,页面上没有什么特别的设置,就是default.aspx: 2.编辑之前页面,只有一个内容编辑器部件,和若干 ...
- iOS HTML 字符串中的图片 自适应大小
本文原文地址:http://www.cnblogs.com/qianLL/p/6095988.html 有时候 我们接收数据的时候 后台给的数据室一串HTML 的字符串 但是 我们要显示出来 这 ...
- IOS开发基础知识--碎片3
十二:判断设备 //设备名称 return [UIDevice currentDevice].name; //设备型号,只可得到是何设备,无法得到是第几代设备 return [UIDevice cur ...
- Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...
- Linux命令学习总结:last
命令简介: 该命令用来列出目前与过去登录系统的用户相关信息.指令英文原义:show listing of last logged in users 执行权限 :有些需要特殊权限 指令所在路径: ...
- Ignite 配置更新Oracle JDBC Drive
如果使用Oracle 12C 作为Ignite 的Repository的话,在Repository Createion Wizard的配置过程中,会出现ORA-28040:No matc ...