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框架,比较著名的有PetaPocoDapperServiceStack.OrmLiteSimple.Data。我也曾经试用过ServiceStack.OrmLite,对他的易用性赞不绝口,特别是对其通过代码完全控制数据库的创建和操作的方式印象深刻,如下所示。

  1. class Note
  2. {
  3. [AutoIncrement] // Creates Auto primary key
  4. public int Id { get; set; }
  5.  
  6. public string NoteText { get; set; }
  7. public DateTime? LastUpdated { get; set; }
  8. }
  9.  
  10. static void Main(string[] args)
  11. {
  12. //Using Sqlite DB
  13. var dbFactory = new OrmLiteConnectionFactory(
  14. SqliteFileDb, false, SqliteDialect.Provider);
  15.  
  16. using (var db = dbFactory.Open()) {
  17.  
  18. db.CreateTableIfNotExists<Note>();
  19.  
  20. // Insert
  21. db.Insert(
  22. new Note {
  23. SchemaUri = "tcm:0-0-0",
  24. NoteText = "Hello world 5",
  25. LastUpdated = new DateTime(2013, 1, 5)
  26. });
  27.  
  28. // Read
  29. var notes = db.Where<Note>(new { SchemaUri = "tcm:0-0-0" });
  30. foreach (Note note in notes)
  31. {
  32. Console.WriteLine("note id=" + note.Id + "noteText=" + note.NoteText);
  33. }
  34. }
  35. Console.ReadLine();
  36. }

注:上面示例代码来自博客。  

但最终还是因为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:

  1. int id = GetQueryIntValue("id");
  2. XUser current = XUser.FetchByID(id);
  3. if (current == null)
  4. {
  5. // 参数错误,首先弹出Alert对话框然后关闭弹出窗口
  6. Alert.Show("参数错误!", String.Empty, ActiveWindow.GetHideReference());
  7. return;
  8. }
  9.  
  10. labName.Text = current.Name;
  11. labRealName.Text = current.ChineseName;
  12. labEmail.Text = current.CompanyEmail;
  13. labPersonalEmail.Text = current.PersonalEmail;
  14. labCellPhone.Text = current.CellPhone;
  15. labOfficePhone.Text = current.OfficePhone;
  16. labOfficePhoneExt.Text = current.OfficePhoneExt;
  17. labHomePhone.Text = current.HomePhone;
  18. labRemark.Text = current.Remark;
  19. labEnabled.Text = current.Enabled ? "启用" : "禁用";
  20. labGender.Text = current.Gender;
  21.  
  22. // 表关联查询用户所属的角色列表
  23. XRoleCollection roles = new Select().From(XRole.Schema)
  24. .InnerJoin(XRoleUser.RoleIdColumn, XRole.IdColumn)
  25. .Where(XRoleUser.UserIdColumn).IsEqualTo(current.Id)
  26. .ExecuteAsCollection<XRoleCollection>();
  27.  
  28. StringBuilder sb = new StringBuilder();
  29. foreach (XRole role in roles)
  30. {
  31. sb.AppendFormat("{0},", role.Name);
  32. }
  33. labRole.Text = sb.ToString().TrimEnd(',');
  34.  
  35. // 初始化职称列表的选择项
  36. XJobTitleCollection jobs = new Select().From(XJobTitle.Schema)
  37. .InnerJoin(XJobTitleUser.JobTitleIdColumn, XJobTitle.IdColumn)
  38. .Where(XJobTitleUser.UserIdColumn).IsEqualTo(current.Id)
  39. .ExecuteAsCollection<XJobTitleCollection>();
  40.  
  41. sb = new StringBuilder();
  42. foreach (XJobTitle job in jobs)
  43. {
  44. sb.AppendFormat("{0},", job.Name);
  45. }
  46.  
  47. labJobTitle.Text = sb.ToString().TrimEnd(',');
  48.  
  49. // 所属部门
  50. // 初始化角色复选框列表的选择项
  51. XDeptCollection depts = new Select().From(XDept.Schema)
  52. .InnerJoin(XDeptUser.DeptIdColumn, XDept.IdColumn)
  53. .Where(XDeptUser.UserIdColumn).IsEqualTo(current.Id)
  54. .ExecuteAsCollection<XDeptCollection>();
  55.  
  56. if (depts.Count > 0)
  57. {
  58. labDept.Text = depts[0].Name;
  59. }

Entity Framework:

  1. int id = GetQueryIntValue("id");
  2. User current = DB.Users
  3. .Include(u => u.Roles)
  4. .Include(u => u.Dept)
  5. .Include(u => u.Titles)
  6. .Where(u => u.UserID == id).FirstOrDefault();
  7. if (current == null)
  8. {
  9. // 参数错误,首先弹出Alert对话框然后关闭弹出窗口
  10. Alert.Show("参数错误!", String.Empty, ActiveWindow.GetHideReference());
  11. return;
  12. }
  13.  
  14. labName.Text = current.Name;
  15. labRealName.Text = current.ChineseName;
  16. labCompanyEmail.Text = current.CompanyEmail;
  17. labEmail.Text = current.Email;
  18. labCellPhone.Text = current.CellPhone;
  19. labOfficePhone.Text = current.OfficePhone;
  20. labOfficePhoneExt.Text = current.OfficePhoneExt;
  21. labHomePhone.Text = current.HomePhone;
  22. labRemark.Text = current.Remark;
  23. labEnabled.Text = current.Enabled ? "启用" : "禁用";
  24. labGender.Text = current.Gender;
  25.  
  26. // 用户所属角色
  27. labRole.Text = String.Join(",", current.Roles.Select(r => r.Name).ToArray());
  28.  
  29. // 用户的职称列表
  30. labTitle.Text = String.Join(",", current.Titles.Select(t => t.Name).ToArray());
  31.  
  32. // 用户所属的部门
  33. if (current.Dept != null)
  34. {
  35. labDept.Text = current.Dept.Name;
  36. }

对比:

使用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)

这里就以用户角色为例,首先定义角色的模型类。

  1. public class Role
  2. {
  3. [Key]
  4. public int ID { get; set; }
  5.  
  6. [Required, StringLength(50)]
  7. public string Name { get; set; }
  8.  
  9. [StringLength(500)]
  10. public string Remark { get; set; }
  11.  
  12. public virtual ICollection<User> Users { get; set; }
  13.  
  14. }

然后是用户的模型类:

  1. public class User
  2. {
  3. [Key]
  4. public int ID { get; set; }
  5.  
  6. [Required, StringLength(50)]
  7. public string Name { get; set; }
  8.  
  9. [Required, StringLength(100)]
  10. public string Email { get; set; }
  11.  
  12. [Required, StringLength(50)]
  13. public string Password { get; set; }
  14.  
  15. [Required]
  16. public bool Enabled { get; set; }
  17.  
  18. [StringLength(10)]
  19. public string Gender { get; set; }
  20.  
  21. [StringLength(100)]
  22. public string ChineseName { get; set; }
  23.  
  24. [StringLength(100)]
  25. public string EnglishName { get; set; }
  26.  
  27. [StringLength(200)]
  28. public string Photo { get; set; }
  29.  
  30. [StringLength(50)]
  31. public string QQ { get; set; }
  32.  
  33. [StringLength(100)]
  34. public string CompanyEmail { get; set; }
  35.  
  36. [StringLength(50)]
  37. public string OfficePhone { get; set; }
  38.  
  39. [StringLength(50)]
  40. public string OfficePhoneExt { get; set; }
  41.  
  42. [StringLength(50)]
  43. public string HomePhone { get; set; }
  44.  
  45. [StringLength(50)]
  46. public string CellPhone { get; set; }
  47.  
  48. [StringLength(500)]
  49. public string Address { get; set; }
  50.  
  51. [StringLength(500)]
  52. public string Remark { get; set; }
  53.  
  54. [StringLength(50)]
  55. public string IdentityCard { get; set; }
  56.  
  57. public DateTime? Birthday { get; set; }
  58. public DateTime? TakeOfficeTime { get; set; }
  59. public DateTime? LastLoginTime { get; set; }
  60. public DateTime? CreateTime { get; set; }
  61.  
  62. public virtual ICollection<Role> Roles { get; set; }
  63.  
  64. }

注意,我们在此定义了两个导航属性(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

定义用户和角色之间多对多的关系:

  1. public class AppBoxContext : DbContext
  2. {
  3. public DbSet<User> Users { get; set; }
  4. public DbSet<Role> Roles { get; set; }
  5.  
  6. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  7. {
  8. base.OnModelCreating(modelBuilder);
  9.  
  10. modelBuilder.Entity<Role>()
  11. .HasMany(r => r.Users)
  12. .WithMany(u => u.Roles)
  13. .Map(x => x.ToTable("RoleUsers")
  14. .MapLeftKey("RoleID")
  15. .MapRightKey("UserID"));
  16.  
  17. }
  18. }

用更加通俗的话来解释上面的代码:

1. 一个角色(Role)有很多(HasMany)用户(Users);

2. 每个用户(Users)又有很多(WithMany)角色(Roles);

3. 把这种多对多的关系映射到一张表(RoleUsers),外键分别是RoleID和UserID。

需要注意的是,在Entity Framework不能直接对关联表进行操作,需要通过Role或者User实体来修改添加删除关系。

编写数据库初始化代码

1. 首先在Global.asax中设置数据库初始化类:

  1. protected void Application_Start(object sender, EventArgs e)
  2. {
  3. Database.SetInitializer(new AppBoxDatabaseInitializer());
  4.  
  5. }

2. 定义数据库初始化类:

  1. public class AppBoxDatabaseInitializer : DropCreateDatabaseIfModelChanges<AppBoxContext> // DropCreateDatabaseAlways<AppBoxContext>
  2. {
  3. protected override void Seed(AppBoxContext context)
  4. {
  5. GetUsers().ForEach(u => context.Users.Add(u));
  6. GetRoles().ForEach(r => context.Roles.Add(r));
  7. }
  8.  
  9. private static List<Role> GetRoles()
  10. {
  11. var roles = new List<Role>()
  12. {
  13. new Role()
  14. {
  15. Name = "系统管理员",
  16. Remark = ""
  17. },
  18. new Role()
  19. {
  20. Name = "部门管理员",
  21. Remark = ""
  22. },
  23. new Role()
  24. {
  25. Name = "项目经理",
  26. Remark = ""
  27. },
  28. new Role()
  29. {
  30. Name = "开发经理",
  31. Remark = ""
  32. },
  33. new Role()
  34. {
  35. Name = "开发人员",
  36. Remark = ""
  37. },
  38. new Role()
  39. {
  40. Name = "后勤人员",
  41. Remark = ""
  42. },
  43. new Role()
  44. {
  45. Name = "外包人员",
  46. Remark = ""
  47. }
  48. };
  49.  
  50. return roles;
  51. }
  52.  
  53. private static List<User> GetUsers()
  54. {
  55. string[] USER_NAMES = { "男", "童光喜", "男", "方原柏", "女", "祝春亚", "男", "涂辉", "男", "舒兆国" };
  56. string[] EMAIL_NAMES = { "qq.com", "gmail.com", "163.com", "126.com", "outlook.com", "foxmail.com" };
  57.  
  58. var users = new List<User>();
  59. var rdm = new Random();
  60.  
  61. for (int i = 0, count = USER_NAMES.Length; i < count; i += 2)
  62. {
  63. string gender = USER_NAMES[i];
  64. string chineseName = USER_NAMES[i + 1];
  65. string userName = "user" + i.ToString();
  66.  
  67. users.Add(new User
  68. {
  69. Name = userName,
  70. Gender = gender,
  71. Password = PasswordUtil.CreateDbPassword(userName),
  72. ChineseName = chineseName,
  73. Email = userName + "@" + EMAIL_NAMES[rdm.Next(0, EMAIL_NAMES.Length)],
  74. Enabled = true,
  75. CreateTime = DateTime.Now
  76. });
  77. }
  78.  
  79. // 添加超级管理员
  80. users.Add(new User
  81. {
  82. Name = "admin",
  83. Gender = "男",
  84. Password = PasswordUtil.CreateDbPassword("admin"),
  85. ChineseName = "超级管理员",
  86. Email = "admin@examples.com",
  87. Enabled = true,
  88. CreateTime = DateTime.Now
  89. });
  90.  
  91. return users;
  92. }
  93. }

开始查询数据库

使用如下代码查询单个用户:

  1. using(var db = new AppBoxContext())
  2. {
  3. int id = Convert.ToInt32(Request.QueryString["id"]);
  4. User current = db.Users
  5. .Include(u => u.Roles)
  6. .Where(u => u.UserID == id).FirstOrDefault();
  7. if (current != null)
  8. {
  9. labName.Text = current.Name;
  10. labRealName.Text = current.ChineseName;
  11. labGender.Text = current.Gender;
  12.  
  13. // 用户所属角色
  14. labRole.Text = String.Join(",", current.Roles.Select(r => r.Name).ToArray());
  15. }
  16. }

但是每次都写using 会觉得很烦,能不能就将AppBoxContext实例存储在一个变量中呢,下面这篇文章给出了最佳实践:

http://stackoverflow.com/questions/6334592/one-dbcontext-per-request-in-asp-net-mvc-without-ioc-container

One DbContext per Request  

我们的实现,在Global.asax的后台代码中:

  1. protected void Application_BeginRequest(object sender, EventArgs e)
  2. {
  3.  
  4. }
  5.  
  6. protected virtual void Application_EndRequest()
  7. {
  8. var context = HttpContext.Current.Items["__AppBoxContext"] as AppBoxContext;
  9. if (context != null)
  10. {
  11. context.Dispose();
  12. }
  13. }

然后在PageBase基类中:

  1. public static AppBoxContext DB
  2. {
  3. get
  4. {
  5. // http://stackoverflow.com/questions/6334592/one-dbcontext-per-request-in-asp-net-mvc-without-ioc-container
  6. if (!HttpContext.Current.Items.Contains("__AppBoxContext"))
  7. {
  8. HttpContext.Current.Items["__AppBoxContext"] = new AppBoxContext();
  9. }
  10. return HttpContext.Current.Items["__AppBoxContext"] as AppBoxContext;
  11. }
  12. }

  

 

下载或捐赠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升级进行时》目录

喜欢这篇文章,请帮忙点击页面右下角的【推荐】按钮。

AppBox升级进行时 - 拥抱Entity Framework的Code First开发模式的更多相关文章

  1. Entity Framework:三种开发模式实现数据访问

    原文地址 http://blog.csdn.net/syaguang2006/article/details/19606715 前言 Entity Framework支持Database First. ...

  2. AppBox升级进行时 - 扁平化的权限设计

    AppBox 是基于 FineUI 的通用权限管理框架,包括用户管理.职称管理.部门管理.角色管理.角色权限管理等模块. AppBox v2.0中的权限实现 AppBox v2.0中权限管理中涉及三个 ...

  3. 【极力分享】[C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例【转载自https://segmentfault.com/a/1190000004152660】

      [C#/.NET]Entity Framework(EF) Code First 多对多关系的实体增,删,改,查操作全程详细示例 本文我们来学习一下在Entity Framework中使用Cont ...

  4. Entity Framework 之 Code First

    使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First [前言] 如果是Code First老鸟或者对Entity Framework不感兴趣,就不用浪费 ...

  5. 在Entity Framework 中用 Code First 创建新的数据库

    在Entity Framework 中用 Code First 创建新的数据库 (原文链接) 本文将逐步介绍怎样用Code First 创建新数据库,使用在代码中定义类和API中提供的特性(Attri ...

  6. Entity Framework 6 Code First新特性:支持存储过程

    Entity Framework 6提供支持存储过程的新特性,本文具体演示Entity Framework 6 Code First的存储过程操作. Code First的插入/修改/删除存储过程 默 ...

  7. 创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表

    创建ASP.NET Core MVC应用程序(3)-基于Entity Framework Core(Code First)创建MySQL数据库表 创建数据模型类(POCO类) 在Models文件夹下添 ...

  8. MVC2、MVC3、MVC4、MVC5之间的区别 以及Entity Framework 6 Code First using MVC 5官方介绍教程

    现在MVC的技术日趋成熟,面对着不同版本的MVC大家不免有所迷惑 -- 它们之间有什么不同呢?下面我把我搜集的信息汇总一下,以便大家能更好的认识不同版本MVC的功能,也便于自己查阅. View Eng ...

  9. Entity Framework Core Code First 项目实践

    Entity Framework Core Code First 实践 任何一种技术的出现都是为了解决一系列特定的问题,只有了解了技术所要解决的关键问题,才能理解它的真正用途,之后,才能在实践中用好它 ...

随机推荐

  1. 在vs2012中用C#开发Android应用Xamarin环境搭建

    Xamarin是Mono创始人Miguel de Icaza创建的公司,旨在让开发者可以用C#编写iOS, Android, Mac应用程序,也就是跨平台移动开发. 简介 Xamarin是基于Mono ...

  2. iOS Version 和 Build 版本号

    Version 和 Build 版本号 开发者都知道,无论是对于 iOS 和 Android 的应用,每个应用都有两个不同的版本号.分别是: Version Build(在 Android 上叫 Ve ...

  3. iOS之处理不等高TableViewCell的几种方法

    课题一:如何计算Cell高度 方案一:直接法(面向对象) 直接法,就是把数据布局到Cell上,然后拿到Cell最底部控件的MaxY值. 第一步:创建Cell并正确设置约束,使文字区域高度能够根据文字内 ...

  4. Android Gson的使用总结

    1.概念 Gson是谷歌发布的一个json解析框架 2.如何获取 github:https://github.com/google/gson android studio使用 compile 'com ...

  5. svn的使用(转载)

    这里只介绍使用CornerStone来使用SVN. CornerStone是Mac OS X系统下非常好用的一款svn工具,当然还有Versions也是可以用的,但是使用起来不如CornerStone ...

  6. python之路径导入

    问题: 最近在学习import的时候,发现不像import xxx,或者from xxx import ooo 这样简单.比如,看下面这个图: 要导入才能在te.py调用pre.tab.py?? 直接 ...

  7. MS SQL 错误:The operation could not be performed because OLE DB provider "SQLNCLI10" for linked server "test" was unable to begin a distributed transaction.

       一同事在测试服务器(系统:Windows 2008 R2 Standard 数据库:SQL SERVER 2008 R2)通过链接服务器test使用分布式事务测试时出错,出错信息如下: set ...

  8. 配置apache的虚拟机+软件下载

    第一步: 打开c:/wamp/apache/conf中的httpd.conf文件, 在httpd.conf中ctrl+f输入vhosts 找到那一行将前面的#号去掉 操作如图所示 第二步: 打开虚拟主 ...

  9. OAF通过Iterator标准遍历各行

    这两天本人接到客户反映的bug:oaf的采购订单页面,在添加超过10行提交后,会出现空指针异常.原来,oaf的默认显示行数为10行,超过10行,页面会分页.报空指针异常,就是因为没有取到分页的行.之前 ...

  10. Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理9

    前两天因有事就没来得及写.今天刚刚好空了.这次写的是对角色和管理员对页面按钮之间的控制.先看页面效果 说明:先根据角色设置好角色的权限,然后管理员在对应的角色下的权限去设置其权限. 在设置角色权限的时 ...