[渣譯文] 使用 MVC 5 的 EF6 Code First 入門 系列:建立一個EF數據模型
2014-04-24 14:50 by Bce, 370 閱讀, 2 評論, 收藏, 編輯
英文渣水平,大伙湊合着看吧……
這是微軟官方SignalR 2.0教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻譯,這里是第一篇:建立一個EF數據模型
原文:Creating an Entity Framework Data Model
譯文版權所有,謝絕全文轉載——但你可以在你的網站上添加到該教程的鏈接。
Contoso大學的Web應用程序
你在本教程中將建立一個簡單的大學網站。
用戶可以查看和更新學生信息,當然也包括教師的。下列圖表是你將創建的應用程序截屏。
本網站的UI樣式來源於內置的模板,所以教程可以將注意力集中在如何使用實體框架上。
創建一個MVC Web應用程序
打開VS並且創建一個新的C#Web項目,命名為ContosoUniversity。
在新建ASP.Net項目對話框中,選擇MVC模板並點擊更改身分驗證按鈕,選擇無身份驗證並確定,創建項目。
設置站點樣式
我們將對站點目錄,布局和主頁面做些細微簡單的改變。
打開 _Layout.cshtml並做以下更改:
- 將"我的 ASP.NET 應用程序"及"應用程序名稱"替換為"Contoso 大學"
- 添加學生、教師、課程和部門的菜單項
完成后,你的代碼應該和下列內容一致
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <meta charset="utf-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>@ViewBag.Title - Contoso 大學</title> 8 @Styles.Render("~/Content/css") 9 @Scripts.Render("~/bundles/modernizr") 10 </head> 11 <body> 12 <div class="navbar navbar-inverse navbar-fixed-top"> 13 <div class="container"> 14 <div class="navbar-header"> 15 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> 16 <span class="icon-bar"></span> 17 <span class="icon-bar"></span> 18 <span class="icon-bar"></span> 19 </button> 20 @Html.ActionLink("Contoso 大學", "Index", "Home", null, new { @class = "navbar-brand" }) 21 </div> 22 <div class="navbar-collapse collapse"> 23 <ul class="nav navbar-nav"> 24 <li>@Html.ActionLink("主頁", "Index", "Home")</li> 25 <li>@Html.ActionLink("關於", "About", "Home")</li> 26 <li>@Html.ActionLink("學生", "Index", "Student")</li> 27 <li>@Html.ActionLink("教師", "Index", "Course")</li> 28 <li>@Html.ActionLink("課程", "Index", "Instructor")</li> 29 <li>@Html.ActionLink("部門", "Index", "Department")</li> 30 </ul> 31 </div> 32 </div> 33 </div> 34 <div class="container body-content"> 35 @RenderBody() 36 <hr /> 37 <footer> 38 <p>© @DateTime.Now.Year - Contoso 大學</p> 39 </footer> 40 </div> 41 42 @Scripts.Render("~/bundles/jquery") 43 @Scripts.Render("~/bundles/bootstrap") 44 @RenderSection("scripts", required: false) 45 </body> 46 </html>
在View\Home\Index.cshtml中,用下面的代碼替換原有的:
1 @{ 2 ViewBag.Title = "Home Page"; 3 } 4 5 <div class="jumbotron"> 6 <h1>Contoso 大學</h1> 7 </div> 8 9 <div class="row"> 10 <div class="col-md-4"> 11 <h2>歡迎訪問Contoso大學</h2> 12 <p> 13 Contoso 大學是一個示例應用程序,演示了如何在MVC5中如何使用實體框架6來建立一個Web應用程序。 14 </p> 15 </div> 16 <div class="col-md-4"> 17 <h2>從頭建立</h2> 18 <p>你可以跟隨教程來一步步建立該應用程序。</p> 19 <p><a class="btn btn-default" href="http://www.cnblogs.com/Bce-/p/3684643.html">查看教程 »</a></p> 20 </div> 21 <div class="col-md-4"> 22 <h2>直接下載</h2> 23 <p>你可以從微軟代碼庫中直接下載已完成的項目</p> 24 <p><a class="btn btn-default" href="http://code.msdn.microsoft.com/ASPNET-MVC-Application-b01a9fe8">Download »</a></p> 25 </div> 26 </div>
按下Ctrl+F5運行網站,你可以看到主頁及菜單。
安裝實體框架6
在工具菜單中,點擊NuGet程序包管理器,點擊程序包管理器控制台。
在控制台中,輸入以下命令並執行:
Install-Package EntityFramework
你可以看到實體框架包被添加到項目中。
創建數據模型
下面你將建立用於大學網站的實體類。你將從以下三個類開始:
注意Student和Enrollment存在一對多的關聯關系。同樣Course和Enrollment也是如此。換句話說,一個學生可以參加任意數量的課程,而一門課程也被任意數量的學生參加。
在下面的章節中我們將開始創建這些實體。
注意:如果你在這些實體類全部創建完成之前嘗試運行項目,你將會得到編譯器錯誤的提示。
學生實體
在Models文件夾下,創建Student類並使用以下的代碼替換:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace ContosoUniversity.Models 7 { 8 public class Student 9 { 10 public int ID { get; set; } 11 public string LastName { get; set; } 12 public string FirstMidName { get; set; } 13 public DateTime EnrollmentDate { get; set; } 14 15 public virtual ICollection<Enrollment> Enrollments { get; set; } 16 } 17 }
ID屬性將成為數據表中對應該類的主鍵列。默認情況下,實體框架將命名為ID或類名+ID的屬性用作主鍵列。
Enrollments屬性是一個導航屬性。導航屬性建立本實體相關的其他實體之間的聯系。在本例中,一個Student實體的Enrollments屬性將容納所有與Student實體相關聯的Entollment實體。換句話說,如果一個Student在數據庫中有兩個相關的Enrollment行(列使用使用該學生的主鍵值作為外鍵),即Student實體的Enrollments屬性將包含這兩個關聯的Enrollment實體。
導航屬性通常被定義為virtual,使他們能獲得某些實體框架的功能,比如延遲加載的優勢。(關於延遲加載我們將在后面解釋)
如果某個導航屬性可以包含多個實體(如多對多或一對多關系),它的類型必須可以進行增刪改操作,比如ICollection。
學生實體
在Models文件夾中,創建Enrollment類並用下面的代碼替換現有的:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace ContosoUniversity.Models 7 { 8 public enum Grade 9 { 10 A, B, C, D, F 11 } 12 13 14 public class Enrollment 15 { 16 public int EnrollmentID { get; set; } 17 public int CourseID { get; set; } 18 public int StudentID { get; set; } 19 public Grade? Grade { get; set; } 20 21 public virtual Course Course { get; set; } 22 public virtual Student Student { get; set; } 23 } 24 }
EnrollmentID屬性將作為主鍵,它使用了類名+ID的模式而不是你在Student中直接使用ID本身的方法。通常情況下你應當在這兩種方式中選擇一種作為整個項目的統一命名方式,在這里我們只是演示了這兩種方式的使用。在后面的教程中你將看到如何使用不帶類名的ID從而更容易地在數據模型中實現繼承。
Grade屬性是一個枚舉。屬性后的問號表示這是一個可為空的屬性。Null表示一個未知或沒有分配的級別。
StudentID是一個外鍵,相應的導航屬性是Student。一個Enrollment實體關聯到一個Student實體。所以屬性只能容納一個Student實體(而不像之前的Student.Enrollments導航屬性,它可以容納多個Enrollments實體)。
同樣CourseID也是一個外鍵,關聯到一個Course實體。
如果一個屬性的命名方式為導航屬性名+主鍵屬性名,實體框架便會將該屬性視為外鍵屬性。(例如,Student實體的主鍵為ID,則StudentID被視為為Student導航屬性的外鍵)。外鍵的屬性也可以命名為簡單的主鍵屬性名(例如,Course實體的主鍵為CourseID)。
課程實體
在Models文件夾中創建Course類並使用以下的代碼替換模板代碼:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.DataAnnotations.Schema; 4 using System.Linq; 5 using System.Web; 6 7 namespace ContosoUniversity.Models 8 { 9 public class Course 10 { 11 [DatabaseGenerated(DatabaseGeneratedOption.None)] 12 public int CourseID { get; set; } 13 public string Title { get; set; } 14 public int Credits { get; set; } 15 16 public virtual ICollection<Enrollment> Enrollments { get; set; } 17 } 18 }
Enrollments屬性是一個導航屬性。一個Course實體可以和任意個Enrollment實體關聯。
我們會在后面的教程中介紹更多的關於DatabaseGenerated特性。該特性可以讓你來輸入該實體的主鍵值,而不是讓數據庫自動生成它。
創建數據庫上下文
在一個數據模型中負責協調實體框架功能的主類被稱為數據庫上下文類。您可以通過派生自System.Data.Entity.DbContext類來創建。你可以在代碼中指定那些實體被包含在數據模型中。您可以可以自定義某些實體框架的行為。在本項目中,上下文類被命名為SchoolContext。
右鍵單擊解決資源方案管理器中的項目,點擊添加,點擊新建文件夾。將新文件夾命名為DAL(數據訪問層)。在該文件夾中創建一個SchoolContext類並使用以下代碼替換模板代碼:
1 using ContosoUniversity.Models; 2 using System; 3 using System.Collections.Generic; 4 using System.Data.Entity; 5 using System.Data.Entity.ModelConfiguration.Conventions; 6 using System.Linq; 7 using System.Web; 8 9 namespace ContosoUniversity.DAL 10 { 11 public class SchoolContext : DbContext 12 { 13 public SchoolContext() : base("SchoolContext") { } 14 15 public DbSet<Student> Students { get; set; } 16 public DbSet<Enrollment> Entollments { get; set; } 17 public DbSet<Course> Courses { get; set; } 18 19 protected override void OnModelCreating(DbModelBuilder modelBuilder) 20 { 21 modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 22 } 23 } 24 }
指定實體集
這段代碼為每個實體集合創建了一個DbSet屬性。在實體框架中,一個實體集對應數據庫中的表,一個實體對應數據表中的一行。
你可以省略DbSet<Enrollment>
和DbSet<Course>,實體框架將自動把它們包含進來。因為Student實體引用了Enrollment實體,並且Enrollment實體引用了Course實體。
指定連接字符串
連接字符串(稍后將被添加到web.config文件中)的名稱被傳遞給構造函數。
1 public SchoolContext() : base("SchoolContext") 2 { 3 }
你同樣可以通過傳遞連接字符串而不是存儲在web.config文件的連接字符串名稱本身來指定連接。如果你不指定連接字符串或一個明確的名稱,實體框架將假定連接字符串名稱和類名稱一致,即在本例中,默認的連接字符串名稱為SchoolContext,同你顯示聲明的一致。
指定表名
OnModelCreating方法中的modelBuilder.Convertions.Remove被用來防止生成復數表名。如果你不這樣做,在數據庫中生成的數據表將被命名為Students,Courses及Entrollments。相反,在本例中我們的表名是Student,Course及Enrollment。對於表名稱是否應該使用復數或單數命名模式並沒有明確的要求。在本教程中我們將使用單數形式。重要的一點是,你可以選擇任意的命名方式——通過是否注釋掉該行代碼。
設定初始化數據庫並填充測試數據
當你運行程序時,實體框架可以自動創建(或自動刪除並重新創建)數據表。你可以指定這應該在每次程序運行時進行或僅當模型發生了變化而不與現有的數據庫同步時才進行。你也可以寫一個Seed方法,以便在數據庫初始化后自動填充測試數據到新的數據表中。
默認的行為是只有當該數據庫不存在時才創建(當數據庫已經存在時會拋出一個異常)。在本節中你將指定在每次模型發生變化時都刪除舊數據庫並建立一個新的。在本例中這樣做是適當的。Seed方法將在重新創建后自動填充測試數據。而在生產中通常不希望這樣做從而丟失數據庫中的所有數據。稍后您將看到如何使用Code First Migration來改變數據庫架構,而不是刪除並重新創建。
在DAL文件夾中,創建一個SchoolInitializer類並使用以下代碼替換默認的:
1 using ContosoUniversity.Models; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 7 namespace ContosoUniversity.DAL 8 { 9 public class SchoolInitializer : System.Data.Entity.DropCreateDatabaseIfModelChanges<SchoolContext> 10 { 11 protected override void Seed(SchoolContext context) 12 { 13 var students = new List<Student> 14 { 15 new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")}, 16 new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")}, 17 new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")}, 18 new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")}, 19 new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")}, 20 new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")}, 21 new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")}, 22 new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")} 23 }; 24 25 students.ForEach(s => context.Students.Add(s)); 26 context.SaveChanges(); 27 var courses = new List<Course> 28 { 29 new Course{CourseID=1050,Title="Chemistry",Credits=3,}, 30 new Course{CourseID=4022,Title="Microeconomics",Credits=3,}, 31 new Course{CourseID=4041,Title="Macroeconomics",Credits=3,}, 32 new Course{CourseID=1045,Title="Calculus",Credits=4,}, 33 new Course{CourseID=3141,Title="Trigonometry",Credits=4,}, 34 new Course{CourseID=2021,Title="Composition",Credits=3,}, 35 new Course{CourseID=2042,Title="Literature",Credits=4,} 36 }; 37 courses.ForEach(s => context.Courses.Add(s)); 38 context.SaveChanges(); 39 var enrollments = new List<Enrollment> 40 { 41 new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A}, 42 new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C}, 43 new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B}, 44 new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B}, 45 new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F}, 46 new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F}, 47 new Enrollment{StudentID=3,CourseID=1050}, 48 new Enrollment{StudentID=4,CourseID=1050,}, 49 new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F}, 50 new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C}, 51 new Enrollment{StudentID=6,CourseID=1045}, 52 new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A}, 53 }; 54 enrollments.ForEach(s => context.Enrollments.Add(s)); 55 context.SaveChanges(); 56 } 57 } 58 }
Seed方法將數據庫的上下文對象作為輸入參數,並在方法中的代碼使用該上下文對象將新的實體添加到數據庫中。對於每個實體模型,代碼創建新的實體集合添加他們到相應的DbSet屬性,然后將更改保存到數據庫。它並不是必須在每組實體后立即調用SaveChanges方法。但這樣做有助於你在發生數據庫寫入異常時快速找到問題的根源。
向web.config中添加一個元素來告訴實體框架你將使用初始化類,比如下面的例子:
context 的Type屬性指定了上下文的類名。你應當使用完整的類名和程序集名。同樣databaseInitializer的Type指定了初始化類的名稱。(如果你不想使用EF初始化,你可以在上下文元素中設置disableDatabaseInitialization="true"
)。
作為一種在web.config中設置初始值設定項的替代方法,你可以通過在Global.asax.cs中Application_Start方法中增加Database.SetInitializer語句來實現同樣的功能。
現在應用程序已經設置為在程序首次運行時,對模型和數據庫中的表進行比較,如果有區別,應用程序刪除並重新創建該數據庫。
注意:當你將應用程序部署到生產環境中時,你必須刪除或禁用數據庫重新創建代碼,后面的教程會演示這一點。
使用SQL Server Express LocalDB數據庫
LocalDB是SQL Server Express的一個輕量版本,非常適合用來進行本地測試,但不建議在生產中使用。
打開應用程序的web.config文件,添加數據庫連接字符串,如下面的例子:
你添加的數據庫連接字符串指定實體框架使用LocalDb作為數據庫引擎,建立一個名為ContosoUniversity1.mdf的數據庫(當前數據庫還不存在,EF將自動創建它)。如果你想在你的App_data中存放數據庫文件,您可以添加AttachDBFilename=|DataDirectory|\ContosoUniversity1.mdf到連接字符串。
創建Student控制器和視圖
現在將創建一個網頁來顯示數據,並在請求數據的過程中自動觸發數據庫創建。你首先需要創建一個新的控制器,但在此之前,生成一次項目以將實體類提供給MVC控制器腳手架模型和上下文類。
- 右鍵點擊Controllers文件夾,選擇添加,單擊新建搭建基架項。
- 在對話框中,選擇包含視圖的MVC 5控制器(使用實體框架)
- 在添加控制器對話框,使用下圖的設置,然后添加。
當你點擊添加時,基架將創建student控制器和一組視圖(.cshtml文件)。在未來,當您使用實體框架創建項目時還可以獲得一些附加功能:只需創建第一個模型類而無需創建連接字符串,然后在添加控制器時指定新的上下文類。該基架將創建數據庫上下文類和連接字符串,以及控制器和視圖。 - 在VS中打開StudentController.cs文件,你將看到類中已經有一個實例化的數據庫上下文對象:
private SchoolContext db = new SchoolContext();
1 public ActionResult Index() 2 { 3 return View(db.Students.ToList()); 4 }
Student\Index.cshtml視圖將列表顯示在表格中:
1 @model IEnumerable<ContosoUniversity.Models.Student> 2 3 @{ 4 ViewBag.Title = "Index"; 5 } 6 7 <h2>Index</h2> 8 9 <p> 10 @Html.ActionLink("Create New", "Create") 11 </p> 12 <table class="table"> 13 <tr> 14 <th> 15 @Html.DisplayNameFor(model => model.LastName) 16 </th> 17 <th> 18 @Html.DisplayNameFor(model => model.FirstMidName) 19 </th> 20 <th> 21 @Html.DisplayNameFor(model => model.EnrollmentDate) 22 </th> 23 <th></th> 24 </tr> 25 26 @foreach (var item in Model) { 27 <tr> 28 <td> 29 @Html.DisplayFor(modelItem => item.LastName) 30 </td> 31 <td> 32 @Html.DisplayFor(modelItem => item.FirstMidName) 33 </td> 34 <td> 35 @Html.DisplayFor(modelItem => item.EnrollmentDate) 36 </td> 37 <td> 38 @Html.ActionLink("Edit", "Edit", new { id=item.ID }) | 39 @Html.ActionLink("Details", "Details", new { id=item.ID }) | 40 @Html.ActionLink("Delete", "Delete", new { id=item.ID }) 41 </td> 42 </tr> 43 } 44 45 </table>
- 按下Ctrl+F5運行項目,點擊學生選項卡已查看Seed方法插入的測試數據。
查看數據庫
當你運行學生信息頁面並且程序嘗試存取數據時,實體框架會檢查到沒有已存在的數據庫並嘗試創建一個。然后運行Seed方法向數據庫中填充數據。
你可以使用服務器資源管理器或SQL Server對象管理器來查看數據庫,在本教程中,我們將使用服務器資源管理器。
- 關閉瀏覽器
- 在服務器資源管理器中,展開數據連接,展開School Context(ContosoUniversity),之后展開表,你會看到數據表已經建立,如下圖:
- 右擊Student表並點擊顯示表數據來查看表中的內容。
- 關閉服務器資源管理器。
ContosoUniversity1.mdf 和 .ldf 數據庫文件通常存放在C:\User\你的用戶名文件夾中。
由於你使用了DropCreateDatabaseIfModelChanges初始化器,你現在可以對Student類做一些改變,重新運行應用程序。數據庫會自動重新建立數據表來匹配你所做出的改變。比如如果你添加了EmailAddress屬性到Student類中,重新運行應用程序並打開Student頁面,然后關閉頁面,檢查數據庫表中的數據,你會看到新的EmailAddress列。
約定
因為使用了約定,你用於編寫建立一個完整數據庫的代碼量已經降低到了最少。這些約定已經在之前的教程中被你使用到,或許你沒有意識到你正在使用它們,包括:
- 實體類型的復數形式被用作表名
- 實體屬性名被用作列名
- 被命名為ID或實體名+ID的屬性被用作主鍵。
- 當一個屬性以<導航屬性名><主鍵屬性名>時被用作外鍵(例如,Student實體的主鍵是ID,則StudentID為導航屬性的外鍵)。你也可以使用簡單的<主鍵屬性名>(例如,Enrollment實體的主鍵是EnrollmentID,你可以直接使用EnrollmentID)。
你已經看到,約定可以被覆蓋。例如指定表的名稱不應當使用復數形式,你會看到以后如何明確標記屬性作為外鍵屬性。你將在后面的教程中了解更多有關約定及如何重寫它們。
總結
現在,您已經創建了一個使用實體框架和SQL Server Express LocalDB來存儲和顯示數據的簡單Web應用程序,在后面的教程中,您將學習如何執行基本的CRUD操作。
作者信息
Tom Dykstra - Tom Dykstra是微軟Web平台及工具團隊的高級程序員,作家。
隨筆分類 -MVC
[渣譯文] 使用 MVC 5 的 EF6 Code First 入門 系列:建立一個EF數據模型
2014-04-24 14:50 by Bce, 372 visits, 網摘, 收藏, 編輯jQuery UI 中Tabs Ajax載入時出現Http 304的問題
2013-07-03 15:33 by Bce, 200 visits, 網摘, 收藏, 編輯MVC4中使用反射來生成TryUpdateModel中允許更新的白名單
2013-04-11 17:34 by Bce, 270 visits, 網摘, 收藏, 編輯MVC4下 飛天誠信 EPass1000加密狗采用PRI沖擊響應身份驗證開發(下)
2013-04-10 09:43 by Bce, 316 visits, 網摘, 收藏, 編輯MVC4下 飛天誠信 EPass1000加密狗采用PRI沖擊響應身份驗證開發(上)
2013-04-09 17:24 by Bce, 291 visits, 網摘, 收藏, 編輯jquery Validation的本地化文件
2013-01-04 16:53 by Bce, 217 visits, 網摘, 收藏, 編輯VS2012 MVC4中jquery validate的日期驗證bug
2012-11-21 14:20 by Bce, 582 visits, 網摘, 收藏, 編輯[渣譯文]Ninject:Dependency injection for filters
2012-08-06 16:58 by Bce, 271 visits, 網摘, 收藏, 編輯