這篇日志將演示:
1、利用EF的Code First模式,自動創建數據庫
2、實現簡單的用戶登錄(不考慮安全僅僅是密碼驗證)
為什么選擇EntityFramework
第一,開發常規中小型系統,能夠提高開發效率。
針對小型系統,ORM提高開發效率那是立竿見影。而且linq+lambda的用戶體驗非常棒,讓代碼可讀性大大增強。即使把linq寫得很爛,小系統也無所謂。
針對中型系統,在對ORM有一定了解並且對linq to entity也掌握一定技巧的基礎上,使用ORM還是可以提高開發效率。
第二,對開發人員的sql技巧沒有要求。
針對不同的數據庫,需要寫的sql語句是有差別的。寫linq不用管這個,不會寫sql都沒事。
我不喜歡寫sql,寫得也很爛。不過EF也支持開發人員自己寫sql。
為什么選擇EF的Code First
第一,在開發階段時,可以做到真正的忽略數據庫(沒錯我就是那么討厭數據庫硬編程的方式)。
第二,實體類屬性數據類型的控制,可以很精確 (比如你可以定義實體類的某個屬性的數據類型為枚舉)
第三,項目穩定后,即使想“不通過實體類去更新數據庫表”,也可以。關閉EF對數據庫的檢測,數據庫中手動修改結構,同時手動調整實體類。
第四,EF7中,將只會保留Code First模式,說明官方也認為Code First才是ORM的正確使用方式
數據實體
首先要注意分離數據實體層和數據上下文層。
新建類庫項目,名稱S.Framework.Entity,用來存放映射到數據庫的實體類。
同時為考慮“多個數據庫”的可能,以文件夾進行隔離,在根目錄下創建文件夾Master(一個區別標識,可以按需定義)。這個設計將大大影響后面的架構設計,請務必注意理解。
根據需求抽象出實體模型。
演示簡單的用戶登錄功能,一個用戶表即可。

1 namespace S.Framework.Entity.Master 2 { 3 /// <summary> 4 /// 用戶 5 /// </summary> 6 public class SysUser 7 { 8 /// <summary> 9 /// 主鍵 10 /// </summary> 11 public string ID { get; set; } 12 13 /// <summary> 14 /// 用戶名 15 /// </summary> 16 public string UserName { get; set; } 17 18 /// <summary> 19 /// 登錄密碼 20 /// </summary> 21 public string Password { get; set; } 22 23 /// <summary> 24 /// 是否禁用 25 /// </summary> 26 public bool IsDisabled { get; set; } 27 28 /// <summary> 29 /// 是否刪除 30 /// </summary> 31 public bool IsDeleted { get; set; } 32 33 /// <summary> 34 /// 獲取或設置一個 <see cref="string"/> 值,該值表示實體對象的數據創建者。 35 /// </summary> 36 public virtual string CreateUser { get; set; } 37 38 /// <summary> 39 /// 獲取或設置一個 <see cref="DateTime"/> 值,該值表示實體對象的數據創建時間。 40 /// </summary> 41 public virtual DateTime CreateDate { get; set; } 42 43 /// <summary> 44 /// 獲取或設置一個 <see cref="string"/> 值,該值表示實體對象的數據最后修改者。 45 /// </summary> 46 public virtual string LastModifyUser { get; set; } 47 48 /// <summary> 49 /// 獲取或設置一個 <see cref="DateTime"/> 值,該值表示實體對象的數據最后修改時間。 50 /// </summary> 51 public virtual DateTime? LastModifyDate { get; set; } 52 } 53 }
請一定注意實體類的命名空間。
為區分各實體類的功能,可以套一個文件夾進行分類,比如System、Basic、Hr等。如下圖:
請一定注意實體類的命名空間,命名空間的最后一級必須是“數據庫標識”。
這里多講一句,不要在實體類中增加“數據庫映射”相關的特性。因為數據實體和數據核心應該是解耦的,作為數據實體,它不關心使用自己的數據驅動是EF還是dapper,那么就不該在它的身上體現任何“站隊伍”的痕跡。
數據核心
數據核心應該能夠支持多種數據驅動。這里先演示EF的使用。
新建類庫項目,名稱S.Framework.DataCore,然后從nuget上先安裝EntityFramework。
創建結構如下圖:
EntityContexts用來存儲EF的數據庫上下文,因為在實體層定義了Master作為數據庫標識,因此在這里建立相對應的數據庫上下文。

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Data.Entity; 7 using System.Data.Entity.ModelConfiguration.Conventions; 8 9 namespace S.Framework.DataCore.EntityFramework.EntityContexts 10 { 11 /// <summary> 12 /// 數據庫上下文 13 /// </summary> 14 public class MasterEntityContext : DbContext 15 { 16 /// <summary> 17 /// 構造函數 18 /// </summary> 19 /// <param name="nameOrConnectionString">數據庫名稱或連接字符串。</param> 20 public MasterEntityContext(string nameOrConnectionString) 21 : base(nameOrConnectionString) 22 { } 23 24 /// <summary> 25 /// 模型配置重寫 26 /// </summary> 27 /// <param name="modelBuilder">數據實體生成器</param> 28 protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) 29 { 30 // 禁用一對多級聯刪除 31 modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 32 // 禁用多對多級聯刪除 33 modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); 34 // 禁用表名自動復數規則 35 modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 36 } 37 } 38 } 39
數據庫上下文創建之后,還需要關聯數據實體類,這樣EF才能知道“數據庫表應該是哪些實體”。在S.Framework.DataCore中引用S.Framework.Entity,然后在MasterEntityContext的中增加屬性,用來表示對實體的使用。

1 /// <summary> 2 /// 用戶 3 /// </summary> 4 public DbSet<SysUser> Users { get; set; }
通過EF自動創建數據庫
到這步,數據實體、數據上下文都有了,理論上來說,就可以在WebUI層中使用了。為演示EF效果,就先這么使用吧,后面再慢慢地完善。
首先,UI層需引用Entity和DataCore。然后在UI層的web.config文件中配置數據庫信息。
注意一下,EF-CodeFirst模式能夠自動創建數據庫,所以在配置數據庫連接字符串的時候,設置一個不存在的數據庫名是完全沒問題的。

1 <connectionStrings> 2 <add name="matrixkey" connectionString="server=MATRIXKEY\MATRIXKEYSQL2012;database=S-SevenMaster-1;uid=sa;password=1;" providerName="System.Data.SqlClient" /> 3 </connectionStrings>
還需要在UI層也引用EntityFramework(這個耦合會在后面移除掉,此處為做演示先引用),然后修改HomeController來嘗試操作數據庫。

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 7 using S.Framework.Entity.Master; 8 using S.Framework.DataCore.EntityFramework.EntityContexts; 9 10 namespace S.Framework.WebClient.Controllers 11 { 12 public class HomeController : Controller 13 { 14 public ActionResult Index() 15 { 16 var db = new MasterEntityContext("matrixkey"); 17 18 //初始化用戶實體類,只需要定義不該為空屬性即可 19 //其實String類型的屬性,通過EF映射到數據庫中后,字段都是允許為空的,這個需要通過“實體配置類”來進行控制,下一章節會講。 20 var entity = new SysUser { ID = Guid.NewGuid().ToString(), UserName = "admin", Password = "123456", CreateDate = DateTime.Now, CreateUser = "admin" }; 21 //將用戶對象附加給數據庫上下文(這僅僅是內存級別的操作) 22 db.Users.Add(entity); 23 //數據庫上下文保存更改(提交變更到數據庫執行) 24 db.SaveChanges(); 25 26 return View(); 27 } 28 } 29 }
編譯生成,運行。如果成功打開/Home/Index頁面,則表示執行成功。可以打開數據庫,檢查是否創建了名為“S-SevenMaster-1”的數據庫,同時檢查該數據庫下是否創建了SysUser表,並且里面有一條admin數據。
__MigrationHistory表是EF自動生成的用來記錄“數據庫結構變更操作”的歷史表,不用管它。有興趣的讀者可以研究一下,也很有意思。
用戶登錄
先注釋HomeController中那段往數據庫里添加admin用戶的代碼,不然每次打開/Home/Index頁面都會新增admin用戶信息。
先有模型后有天,要登錄先定義登錄頁面的表單數據模型吧。可以在Models文件夾建立LoginModel類。

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace S.Framework.WebClient.Models 7 { 8 /// <summary> 9 /// 登錄數據模型 10 /// </summary> 11 public class LoginModel 12 { 13 /// <summary> 14 /// 登錄用戶名 15 /// </summary> 16 public string UserName { get; set; } 17 18 /// <summary> 19 /// 登錄密碼 20 /// </summary> 21 public string Password { get; set; } 22 23 /// <summary> 24 /// 提示信息 25 /// </summary> 26 public string Message { get; set; } 27 } 28 }
然后創建AccountController,並定義登錄頁面的Action如下:

1 public ViewResult Login() 2 { 3 var model = new S.Framework.WebClient.Models.LoginModel(); 4 5 return View(model); 6 }
右鍵Login這個Action創建視圖,選擇不需要布局頁。登錄界面不是重點,此處就不貼頁面代碼一筆帶過了。
在控制器中還需定義“登錄操作”的Action,邏輯暫時比較簡單,直接上代碼:

1 [ValidateAntiForgeryToken] 2 [HttpPost] 3 public ActionResult Login(S.Framework.WebClient.Models.LoginModel model) 4 { 5 var db = new MasterEntityContext("matrixkey"); 6 var entity = db.Users.Where(w => w.UserName == model.UserName).FirstOrDefault(); 7 if (entity == null) 8 { 9 model.Message = "用戶名不存在"; 10 } 11 else 12 { 13 if (entity.Password != model.Password) 14 { 15 model.Message = "密碼輸入不正確"; 16 } 17 else 18 { 19 return RedirectToAction("Index", "Home", new { }); 20 } 21 } 22 23 return View(model); 24 }
這里注意2個特性的作用。ValidateAntiForgeryToken是用來阻止偽造的登錄請求的,需要視圖中有相應信息配合使用。HttpPost是用於區別“登錄頁面的Login和登錄操作的Login”,需要視圖中表單的提交方式也是Post。順帶一句,控制器中的Action是不能重載的,但可以利用表示HTTP方法的特性加以區分。
身份驗證的功能,下面單獨拎出來講,先跑通密碼驗證的邏輯。
數據模型、登錄頁面、登錄操作,都已經完成,運行一下效果,用戶admin,密碼123456,成功跳轉到首頁。
說明一下,安全身份驗證的功能將放在后面再講。
因為要演示封裝架構的過程,而現在連數據倉儲層和業務層都沒有創建,不方便單獨寫身份驗證部分的代碼。
下一章節,將演示EF實體配置的使用和利用T4模板自動生成實體配置。
截止本章節,項目源碼下載:點擊下載(存在百度雲盤中)