1. 程式人生 > >擁抱Entity Framework的Code First開發模式

擁抱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框架,比較著名的有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)做後端資料操作,簡直就是絕配。 

使用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來配置模型類的關係

定義使用者和角色之間多對多的關係:

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 = "[email protected]",
			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

2. AppBox v3.0 是捐贈軟體,你可以通過捐贈作者來獲取AppBox v3.0的全部原始碼(http://fineui.com/donate/)。

喜歡這篇文章,請幫忙點選頁面右下角的【推薦】按鈕。

相關推薦

擁抱Entity Framework的Code First開發模式

AppBox 是基於 FineUI 的通用許可權管理框架,包括使用者管理、職稱管理、部門管理、角色管理、角色許可權管理等模組。 從Subsonic到Entity Framework Subsonic最早釋出於2008年,當時他的無程式碼生成模式吸引了很多人的眼球,ActiveRecord模式的支援

EF3:Entity Framework三種開發模式實現數據訪問

支持 代碼 sql blog flow cli guid 自動完成 main 前言 Entity Framework支持Database First、Model First和Code Only三種開發模式,各模式的開發流程大相徑庭,開發體驗完全不一樣。三種開發模式各有優缺

Entity Framework技術系列之2:三種開發模式實現資料訪問

前言 Entity Framework支援Database First、Model First和Code Only三種開發模式,各模式的開發流程大相徑庭,開發體驗完全不一樣。三種開發模式各有優缺點,對於程式設計師沒有哪種模式最好,只有哪種模式更適合。接下來我將分別使用這

百鳥商城系統開發模式設計詳解

png 團隊 收入 出現 積分 完全 會員 .cn 更多 百鳥商城系統開發(李想.185.6504.8478)鳥類通常是帶羽、卵生的動物,有極高的新陳代謝速率,長骨多是中空的,所以大部分的鳥類都可以飛。鳥類由爬行動物進化而來,世界上現存的鳥類共有9000多種,它們都有翅膀和

萌店系統開發模式系統詳解

app 上海 好的 供應鏈 實現 即時通訊 消費者 粉絲 統計數據 萌店系統開發(李想.185.6504.8478)O2O的優勢在於把網上和網下的優勢完美結合。通過網購導購機,把互聯網與地面店完美對接,實現互聯網落地。讓消費者在享受線上優惠價格的同時,又可享受線下貼身的服務

如何讓Entity Framework Db Frist模式下的Entity繼承關系?

clas use hide closed 相關 ase 創建 color 修改 1、使用DB Frist模式創建實體數據模型 Db Frist創建實體數據模型(創建edmx並不是重點,各位隨意即可),此處取名ZeroCodeDB,所得文件如圖所示; 其中紅框中的文件(Z

小程序商城系統開發模式平臺搭建詳解

小程序 效果 商機 應用場景 模式 找到 成本 時代 哪些 小程序商城系統開發(李想.185.6504.8478)隨著移動互聯網的深入普及,流量碎片化的趨勢無法逆轉,百度、淘寶等平臺成本越來越高,效果卻越來越差,企業單一流量入口保持增長的時代結束。小程序二維碼多入口的訪問形

weblogic-開發模式轉變為生產模式&&生產模式轉變為開發模式

weblogic 在實際生產環境中,我們部署weblogic,一般都是配置成生產模式,理由是便於我們後期的管理,只有在我們的人為控制下,才能執行發布項目等操作。而weblogic的開發模式,類似tomcat的自動發布,定期檢測對應目錄下的程序文件是否有更新,如果更新則會自動發布,這個可以用於項目比較小的

敏捷開發模式下的測試

定義 談判 要求 信心 時間 質量 計劃 活性 程序 敏捷開發   敏捷開發倡導的就是叠代式和增量式的開發模式,並且強調測試在開發過程中的重要性 。主要是圍繞以用戶為中心,以客戶需求為導向的開發過程,這個過程有一個特點就是“隨時有變化”。雖然敏捷開發引入了靈活性,但其中的重

《Head First 設計模式》學習筆記——復合模式

listener 解讀 out 部件 register != file 窗體 event 模型-視圖-控制器(MVC模式)是一種很經典的軟件架構模式。在UI框架和UI設計思路中扮演著很重要的角色。從設計模式的角度來看,MVC模式是一種復合模式。它將多個設計模式在

微信開發模式無法驗證以及返回消息中文亂碼的情況

dsm 中文 第一次 下載 pos 亂碼 公眾平臺 art style 一開始我也糾結了這個問題非常久,從微信公眾平臺上下載下來的樣例不是utf-8格式的,可是卻能夠驗證通過。 此時改動增加中文,返回消息會亂碼,改成utf-8編碼就顯示正常了。 再

微信公眾號開發系列-啟用開發模式

sum oca 使用 popu 接口交互 開發模式 signature 微信公眾 local 微信公眾平臺分為兩種模式:編輯模式與開發模式; 微信公眾帳號申請成功後,要想用程序接收處理用戶的請求,就必需要在“高級功能”裏進行配置。點擊“高級功能”。 從微信開發平臺開發人

房地產開發模式

項目 設計 產品設計 地產 模式 其他 開發 傳統 一般模式 1.傳統的房地產開發模式 一般模式:獲取土地→產品設計→項目建造→項目銷售→交樓物管 2.傳統的房地產開發模式 其他模式:獲取房產→產品設計→項目

小程序共享鏈系統開發模式

image 整合 排序 廣告 商品 什麽 營銷 http 資源 小程序共享鏈系統模式 小程序共享鏈模式系統 150-1305-3356(小麗)小程序共享鏈平臺系統 小程序共享鏈軟件 小程序共享鏈定制共享鏈是一款營銷工具,解決實體店現有的營銷方案難以刺激消費者、被電商的打壓

Head First設計模式之模板方法模式

names 去除 缺點 ide 個數 write ima 父類 public 一、定義 在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中,使得子類可以不改變算法結構的情況下,重定義該算法中的某些特定步驟。 比較通俗的說法,子類決定如何實現算法中的某些步驟,比如兩

JavaBean+jsp開發模式 --結合form表單 實例

fff checkbox oct ima ring pub meta 結合 text 1.創建form表單 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncodi

Head First設計模式之外觀模式

實現 add ads important pac mar private 入口 summary 一、定義 外觀模式提供了一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。 外觀模式不只是簡化了接口,也將客戶從組件的子系統中解耦。

web開發模式小結:頁面亂碼和跳轉

ati 字符 -- 默認 htm *** 亂碼 控制 ram 本文由付老師總結書寫 java開發模式: (1)第一種開始模式:javaBean+jsp : 優點:可以為web程序在jsp中減少java代碼量 適用於該開發模式的

微信開發模式之自己定義菜單實現

ces event pre api pid 開發人員 web weixin src 編輯模式和開發模式是有沖突的。所以我們啟用微信公眾號的開發模式之後。那些菜單是看不到的哦。只是如今個人訂閱號是不能夠使用高級開發人員模式的。如自己定義菜單,只是我們還是能夠通過測試號

Head First設計模式之代理模式

collect prot indent margin 虛擬代理 ans sig smart ati 一、定義 定義:為其他對象提供一種代理以控制對這個對象的訪問 在代理模式中,我們創建具有現有對象的對象,以便向外界提供功能接口。 二、結構 代理模式一般會有三個角