zoukankan      html  css  js  c++  java
  • AppBox升级进行时

    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,对他的易用性赞不绝口,特别是对其通过代码完全控制数据库的创建和操作的方式印象深刻,如下所示。

    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实例存储在一个变量中呢,下面这篇文章给出了最佳实践:

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

    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升级进行时》目录

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

  • 相关阅读:
    【LeetCode】085. Maximal Rectangle
    哈希查找
    堆排序
    归并排序
    希尔排序
    快速排序
    堆区和栈区,malloc和new的区别
    C++基础
    二分查找
    冒泡排序
  • 原文地址:https://www.cnblogs.com/sanshi/p/3274427.html
Copyright © 2011-2022 走看看