AppBox升級進行時 - 擁抱Entity Framework的Code First開發模式

来源:http://www.cnblogs.com/wufei999/archive/2017/04/14/6710068.html
-Advertisement-
Play Games

小分享:我有幾張阿裡雲優惠券,用券購買或者升級阿裡雲相應產品最多可以優惠五折!領券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 AppBox 是基於 FineUI 的通用權 ...


小分享:我有幾張阿裡雲優惠券,用券購買或者升級阿裡雲相應產品最多可以優惠五折!領券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03


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 = "[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實例存儲在一個變數中呢,下麵這篇文章給出了最佳實踐:

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升級進行時》目錄

 

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

 

參考頁面:http://qingqingquege.cnblogs.com/p/5933752.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、前言 通常我們的項目會包含許多對外的介面,這些介面都需要文檔化,標準的介面描述文檔需要描述介面的地址、參數、返回值、備註等等;像我們以前的做法是寫在word/excel,通常是按模塊劃分,例如一個模塊包含n個介面,就形成一個文檔,然後再用版本控制管理。這樣做的缺點是: 1.不夠直觀,每次打開文檔 ...
  • MVC + Web API + AngularJs 搭建簡單的 CURD 框架 GitHub 地址:https://github.com/liqingwen2015/Wen.MvcSinglePage 佈局頁的引用 app.js app-route.js app-service.js demoCon ...
  • jsp
    動態網站的優勢: 1.交互性:即網頁會根據用戶的要求和選擇而動態改變和顯示內容 2.自動更新:即無需改變頁面代碼,便會自動生成新的頁面內容,可以大大節省工作量 3.隨機性:即當不同的時間,不同的人訪問同一個網址時會產生不同的頁面效果 目錄 說明 /bin 存放各種平臺下用於啟動和停止Tomcat的腳 ...
  • 1.動態網頁的優勢? ①交互性:即網頁會根據用戶的要求和選擇而動態改變和顯示內容. ③自動更新:即無需改變頁面代碼,便會自動生成新的頁面內容. ④隨機性:即當不同的時間、不同的人訪問一網址時會產生不同的頁面效果. 4.什麼是動態網頁? 動態網頁是指在伺服器端運行的,使用程式語言設計的互動式網頁, 它 ...
  • 增加動態輸出 整個web應用平臺的關註點在於構建並顯示動態輸出內容。在MVC里,控制器負責構建一些數據並將其傳給視圖。視圖負責渲染成HTML。 從控制器向視圖傳遞數據的一種方式是使用ViewBag 對象,它是一個控制器基類的成員。ViewBag是一個動態對象,你可以給他賦值任意屬性給視圖來渲染用。代... ...
  • VIM一般分幾種模式,通過不同模式來區分輸入的到底是文字還是命令:1. Normal mode(common mode,以下簡稱 c-mode):一進入 VIM 就是處於 c-mode,只能輸入指令,不能輸入文字。這些指令可能是游標移動的指令,也可能是編輯指令或尋找替換指令。2. Insert mo ...
  • 支付基本上是很多產品都必須的一個模塊,大家最熟悉的應該就是微信和支付寶支付了,不過更多的可能還是停留在直接sdk的調用上,甚至和業務系統高度耦合,網上也存在各種解決方案,但大多形式各異,東拼西湊而成。所以這裡我介紹下OSS.PayCenter開源跨平臺支付組件 及其框架設計。並對常用支付模式進行一個 ...
  • 小分享:我有幾張阿裡雲優惠券,用券購買或者升級阿裡雲相應產品最多可以優惠五折!領券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 目錄索引 【無私分享:ASP.NET COR ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...