Contoso University示例網站演示如何使用Entity Framework 5建立ASP.NET MVC 4應用程式。
Entity Framework有三種處理資料的方式: Database First, Model First, and Code First. 本指南使用程式碼優先。其它方式請查詢資料。
示例程式是為Contoso University建立一個網站。功能包括:學生管理、課程建立、教師分配。 本系列指南逐步講述如何實現這一網站程式。
本示例程式基於 ASP.NET MVC.如果使用 ASP.NET Web Forms model, 請檢視 Model Binding and Web Forms系列指南和 ASP.NET Data Access Content Map.
如有問題,可在這些討論區提問: ASP.NET Entity Framework forum, the Entity Framework and LINQ to Entities forum, or StackOverflow.com.
(此指南的舊版本請檢視 the EF 4.1 / MVC 3 e-book.)
Contoso University 應用程式
本指南將建立一個簡單的大學網站.
使用者可檢視或更新學生、課程、教師的資訊,以下是相關截圖:
UI風格延續了預設模板的風格,以便更多關注於如何使用Entity Framework。
需求
使用 Visual Studio 2012 or Visual Studio 2012 Express for Web, 可從以下連結獲取相關需求軟體:
Windows Azure SDK for Visual Studio 2012
If you have Visual Studio installed, the link above will install any missing components. If you don't have Visual Studio, the link will install Visual Studio 2012 Express for Web.
若使用 Visual Studio 2010,需要安裝MVC 4 和 SQL Server LocalDB.
建立MVC Web程式
建立程式如下:
選擇 Internet Application template.
選擇 Razor
Click OK.
設定網站風格
選單、佈局有少許變動.
開啟Views\Shared\_Layout.cshtml, 修改如下:黃色為修改後內容.
<!DOCTYPE html><htmllang="en"><head><metacharset="utf-8"/><title>@ViewBag.Title - Contoso University</title><linkhref="~/favicon.ico"rel="shortcut icon"type="image/x-icon"/><metaname="viewport"content="width=device-width"/>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head><body><header><divclass="content-wrapper"><divclass="float-left"><pclass="site-title">@Html.ActionLink("Contoso University", "Index", "Home")</p></div><divclass="float-right"><sectionid="login">
@Html.Partial("_LoginPartial")
</section><nav><ulid="menu"><li>@Html.ActionLink("Home", "Index", "Home")</li><li>@Html.ActionLink("About", "About", "Home")</li><li>@Html.ActionLink("Students", "Index", "Student")</li><li>@Html.ActionLink("Courses", "Index", "Course")</li><li>@Html.ActionLink("Instructors", "Index", "Instructor")</li><li>@Html.ActionLink("Departments", "Index", "Department")</li></ul></nav></div></div></header><divid="body">
@RenderSection("featured", required: false)
<sectionclass="content-wrapper main-content clear-fix">
@RenderBody()
</section></div><footer><divclass="content-wrapper"><divclass="float-left"><p>© @DateTime.Now.Year - Contoso University</p></div></div></footer> @Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body></html>
上面做了兩點改變:
- 把 "My ASP.NET MVC Application" 和"your logo here" 替換為"Contoso University".
- 新增一些後面用到的超連結
在Views\Home\Index.cshtml, 替換為如下程式碼:
@{
ViewBag.Title = "Home Page";
}
@section featured {
<sectionclass="featured"><divclass="content-wrapper"><hgroupclass="title"><h1>@ViewBag.Title.</h1><h2>@ViewBag.Message</h2></hgroup></div></section>
}
在 Controllers\HomeController.cs, 把 ViewBag.Message
值替換為 "Welcome to Contoso University!":
publicActionResultIndex(){ViewBag.Message="Welcome to Contoso University";returnView();}
CTRL+F5 執行,介面如下.
建立資料模型
先建立如下三個資料模型:
Student
and Enrollment
實體是一對多關係,, Course
and Enrollment
實體也是一對多關係. 也就是說一個學生可以註冊多門課程,一門課程允許多位學生註冊。
為每個實體建立對應的類:
Note If you try to compile the project before you finish creating all of these entity classes, you'll get compiler errors.
Student Entity
在Models資料夾建立Student.cs ,程式碼如下:
usingSystem;usingSystem.Collections.Generic;namespaceContosoUniversity.Models{publicclassStudent{publicintStudentID{get;set;}publicstringLastName{get;set;}publicstringFirstMidName{get;set;}publicDateTimeEnrollmentDate{get;set;}publicvirtualICollection<Enrollment>Enrollments{get;set;}}}
StudentID
屬性將成為資料表的主鍵列。預設Entity Framework將名為ID或者類名ID的屬性設為主鍵。
Enrollments
是一個導航屬性。導航屬性記錄和本實體相關的其它實體。在本例中,Enrollments
屬性記錄和 Student
屬性相關的Enrollment。.如果資料庫中某Student記錄和
兩條Enrollment記錄
相關。(這兩條記錄的 StudentID
外來鍵值等於該Student的StudentID)
,那麼Student
實體的 Enrollments
導航屬性將包含這兩個 Enrollment
實體.
Navigation properties 常定義為virtual
以便發揮Entity Framework的功能,如 lazy loading. (在 Reading Related Data 部分將詳細講述延遲載入).
如果navigation property 包含多記錄 (如 many-to-many or one-to-many 關係), 型別最好是列表型別,如 ICollection
.
The Enrollment Entity
在Models資料夾, 建立 Enrollment.cs,程式碼如下:
namespaceContosoUniversity.Models{publicenumGrade{
A, B, C, D, F
}publicclassEnrollment{publicintEnrollmentID{get;set;}publicintCourseID{get;set;}publicintStudentID{get;set;}publicGrade?Grade{get;set;}publicvirtualCourseCourse{get;set;}publicvirtualStudentStudent{get;set;}}}
Grade型別
後面的問號表示Grade
屬性是 nullable.
StudentID
property 是外來鍵, 相應的導航屬性是 Student
.一個 Enrollment
實體和一個 Student
實體相關,
CourseID
property 是外來鍵, 相應的導航屬性是 Course
.
The Course Entity
In the Models folder, create Course.cs, replacing the existing code with the following code:
usingSystem.Collections.Generic;usingSystem.ComponentModel.DataAnnotations.Schema;namespaceContosoUniversity.Models{publicclassCourse{[DatabaseGenerated(DatabaseGeneratedOption.None)]publicintCourseID{get;set;}publicstringTitle{get;set;}publicintCredits{get;set;}publicvirtualICollection<Enrollment>Enrollments{get;set;}}}
Enrollments
屬性是導航屬性. A Course
實體對應多個 Enrollment
實體.
下一節再對 [DatabaseGenerated(DatabaseGeneratedOption.None)]
特性進行講解。 簡而言之,此特性表明主鍵由你賦值而非資料庫自動生成。
建立Database Context
將 Entity Framework 功能和給定資料模型相關聯的類是 database context class. 此類繼承自 System.Data.Entity.DbContext class.程式碼表明資料模型包含了哪些實體型別.本專案中資料上下文類名為 SchoolContext
.
建立 DAL資料夾 (for Data Access Layer). 在此資料夾建立SchoolContext.cs,程式碼如下:
usingContosoUniversity.Models;usingSystem.Data.Entity;usingSystem.Data.Entity.ModelConfiguration.Conventions;namespaceContosoUniversity.DAL
{publicclassSchoolContext:DbContext{publicDbSet<Student>Students{get;set;}publicDbSet<Enrollment>Enrollments{get;set;}publicDbSet<Course>Courses{get;set;}protectedoverridevoidOnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();}}}
程式碼為每一個實體集合建立 DbSet 屬性。. 在Entity Framework,實體集合對應資料表,一個實體對應表中的一條記錄。.
modelBuilder.Conventions.Remove
語句阻止表名使用實體的複數形式,如果沒有此語句,則生成的資料表分別是 Students
, Courses
, andEnrollments
. 使用此語句,表名將和實體名一樣 Student
, Course
, and Enrollment
. 這和程式設計風格相關,至於是否使用複數取決於你自己。
SQL Server Express LocalDB
LocalDB 是一個輕量級的SQL Server。這裡不做翻譯介紹。
開啟根目錄下的 Web.config 檔案,在 connectionStrings
處新增連線字串如下,(注意,如果沒有localdb,而使用SQL Server或者Express版本,請修改連線字串)
<addname="SchoolContext"connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\ContosoUniversity.mdf"providerName="System.Data.SqlClient"/>
預設Entity Framework尋找和資料上下文類同名的連線字串 (SchoolContext
for this project). 更多連線字串資訊,請檢視 SQL Server Connection Strings for ASP.NET Web Applications.
也可不新增連線字串,由程式自動生成。但會導致資料庫檔案沒有放在程式的 App_data資料夾下,更多資訊請檢視 Code First to a New Database.
connectionStrings
集合預設包含一個名為 DefaultConnection的連線字串,是用來連線
membership database. 這裡不會用到。兩條連線字串唯一不同之處是資料庫名字不同
設定並執行 Code First Migration
在程式初期,資料模型經常發生變動,每次變動就會導致和資料庫不一致。可將Entity Framework配置為變動後自動重建資料庫。但在程式使用之後如果發生變動,更希望是更新資料庫而非重建(重建導致資料丟失)。 Migrations 功能使得程式碼優先方式下更新資料庫。如果希望重建可使用DropCreateDatabaseIfModelChanges實現每次變動後重建資料庫。. 本例中我們直接使用Migration方法,更多資訊請檢視 Code First Migrations.
啟用Code First Migrations
工具選單,選擇Library Package Manager ,Package Manager Console.
PM>
提示符下輸入如下命令:enable-migrations -contexttypename SchoolContext
命令將建立 Migrations資料夾,並在資料夾下建立Configuration.cs.
Configuration
類包含Seed
方法,資料庫建立或更新後將呼叫此方法。internalsealedclassConfiguration:DbMigrationsConfiguration<ContosoUniversity.Models.SchoolContext>{publicConfiguration(){AutomaticMigrationsEnabled=false;}protectedoverridevoidSeed(ContosoUniversity.Models.SchoolContext context){// This method will be called after migrating to the latest version.// You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g.//// context.People.AddOrUpdate(// p => p.FullName,// new Person { FullName = "Andrew Peters" },// new Person { FullName = "Brice Lambson" },// new Person { FullName = "Rowan Miller" }// );//}}
Seed
方法使得可設定自動插入到資料庫中的資料
設定Seed方法
為了便於測試,我們在Seed中新增一些資料
- 替換 Configuration.cs內容如下:
namespaceContosoUniversity.Migrations{usingSystem;usingSystem.Collections.Generic;usingSystem.Data.Entity.Migrations;usingSystem.Linq;usingContosoUniversity.Models;internalsealedclassConfiguration:DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext>{publicConfiguration(){AutomaticMigrationsEnabled=false;}protectedoverridevoidSeed(ContosoUniversity.DAL.SchoolContext context){var students =newList<Student>{newStudent{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2010-09-01")},newStudent{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2012-09-01")},newStudent{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2013-09-01")},newStudent{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2012-09-01")},newStudent{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2012-09-01")},newStudent{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2011-09-01")},newStudent{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2013-09-01")},newStudent{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-08-11")}};
students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
context.SaveChanges();var courses =newList<Course>{newCourse{CourseID=1050,Title="Chemistry",Credits=3,},newCourse{CourseID=4022,Title="Microeconomics",Credits=3,},newCourse{CourseID=4041,Title="Macroeconomics",Credits=3,},newCourse{CourseID=1045,Title="Calculus",Credits=4,},newCourse{CourseID=3141,Title="Trigonometry",Credits=4,},newCourse{CourseID=2021,Title="Composition",Credits=3,},newCourse{CourseID=2042,Title="Literature",Credits=4,}};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.Title, s));
context.SaveChanges();var enrollments =newList<Enrollment>{newEnrollment{StudentID= students.Single(s => s.LastName=="Alexander").StudentID,CourseID= courses.Single(c => c.Title=="Chemistry").CourseID,Grade=Grade.A
},newEnrollment{StudentID= students.Single(s => s.LastName=="Alexander").StudentID,CourseID= courses.Single(c => c.Title=="Microeconomics").CourseID,Grade=Grade.C
},newEnrollment{StudentID= students.Single(s => s.LastName=="Alexander").StudentID,CourseID= courses.Single(c => c.Title=="Macroeconomics").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Alonso").StudentID,CourseID= courses.Single(c => c.Title=="Calculus").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Alonso").StudentID,CourseID= courses.Single(c => c.Title=="Trigonometry").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Alonso").StudentID,CourseID= courses.Single(c => c.Title=="Composition").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Anand").StudentID,CourseID= courses.Single(c => c.Title=="Chemistry").CourseID},newEnrollment{StudentID= students.Single(s => s.LastName=="Anand").StudentID,CourseID= courses.Single(c => c.Title=="Microeconomics").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Barzdukas").StudentID,CourseID= courses.Single(c => c.Title=="Chemistry").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Li").StudentID,CourseID= courses.Single(c => c.Title=="Composition").CourseID,Grade=Grade.B
},newEnrollment{StudentID= students.Single(s => s.LastName=="Justice").StudentID,CourseID= courses.Single(c => c.Title=="Literature").CourseID,Grade=Grade.B
}};foreach(Enrollment e in enrollments){var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.StudentID== e.StudentID&&
s.Course.CourseID== e.CourseID).SingleOrDefault();if(enrollmentInDataBase ==null){
context.Enrollments.Add(e);}}
context.SaveChanges();}}}由於此方法在建立或更新後呼叫,為了避免多次插入同一資料,呼叫AddOrUpdate方法,第一個引數用來檢查資料是否已經存在。
context.Students.AddOrUpdate(p => p.LastName, s)
關於更多AddOrUpdate資訊,請檢視 Take care with EF 4.3 AddOrUpdate Method on Julie Lerman's blog.
foreach(Enrollment e in enrollments){var enrollmentInDataBase = context.Enrollments.Where(
s => s.Student.StudentID== e.Student.StudentID&&
s.Course.CourseID== e.Course.CourseID).SingleOrDefault();if(enrollmentInDataBase ==null){
context.Enrollments.Add(e);}}關於Seed中問題的除錯,請檢視 Seeding and Debugging Entity Framework (EF) DBs on Rick Anderson's blog.
編譯.
建立並執行 First Migration
- 在 the Package Manager Console 執行命令:
add-migration InitialCreate
update-databaseadd-migration
命令將新增 [DateStamp]_InitialCreate.cs 檔案到Migrations資料夾,檔案中包含資料庫建立初始化資訊。第一個引數 (InitialCreate)
作為檔名,前面會加上時間戳.InitialCreate
檔案程式碼如下:namespaceContosoUniversity.Migrations{usingSystem;usingSystem.Data.Entity.Migrations;publicpartialclassInitialCreate:DbMigration{publicoverridevoidUp(){CreateTable("dbo.Student",
c =>new{StudentID= c.Int(nullable:false, identity:true),LastName= c.String(),FirstMidName= c.String(),EnrollmentDate= c.DateTime(nullable:false),}).PrimaryKey(t => t.StudentID);CreateTable("dbo.Enrollment",
c =>new{EnrollmentID= c.Int(nullable:false, identity:true),CourseID= c.Int(nullable:false),StudentID= c.Int(nullable:false),Grade= c.Int(),}).PrimaryKey(t => t.EnrollmentID).ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete:true).ForeignKey("dbo.Student", t => t.StudentID, cascadeDelete:true).Index(t => t.CourseID).Index(t => t.StudentID);CreateTable("dbo.Course",
c =>new{CourseID= c.Int(nullable:false),Title= c.String(),Credits= c.Int(nullable:false),}).PrimaryKey(t => t.CourseID);}publicoverridevoidDown(){DropIndex("dbo.Enrollment",new[]{"StudentID"});DropIndex("dbo.Enrollment",new[]{"CourseID"});DropForeignKey("dbo.Enrollment","StudentID","dbo.Student");DropForeignKey("dbo.Enrollment","CourseID","dbo.Course");DropTable("dbo.Course");DropTable("dbo.Enrollment");DropTable("dbo.Student");}}}The
update-database
執行檔案中的Up
方法建立資料庫,然後呼叫Seed
方法.
名為 ContosoUniversity的資料庫將被建立, .mdf檔案被存放在 App_Data 資料夾,如你在連線字串指定的一致.
以下步驟是在VS中檢視資料庫的操作,按圖所示操作即可,不再翻譯。
From the View menu, click Server Explorer.
Click the Add Connection icon.
If you are prompted with the Choose Data Source dialog, click Microsoft SQL Server, and then clickContinue.
In the Add Connection dialog box, enter (localdb)\v11.0 for the Server Name. Under Select or enter a database name, select ContosoUniversity.
Click OK.
Expand SchoolContext and then expand Tables.
Right-click the Student table and click Show Table Data to see the columns that were created and the rows that were inserted into the table.
建立Student Controller and Views
- 右擊Controllers資料夾,選擇建立Controller,相關引數資訊如下圖所示:
Visual Studio 開啟 Controllers\StudentController.cs file. 資料庫上下文物件已經建立
privateSchoolContext db =newSchoolContext();
Index
action method 從資料庫上下文獲取Students
屬性,返回學生列表:publicViewResultIndex(){returnView(db.Students.ToList());}
The Student\Index.cshtml 檢視顯示了列表中的資訊:
<table><tr><th>
@Html.DisplayNameFor(model => model.LastName)
</th><th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th><th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th><th></th></tr> @foreach (var item in Model) {
<tr><td>
@Html.DisplayFor(modelItem => item.LastName)
</td><td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td><td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td><td>
@Html.ActionLink("Edit", "Edit", new { id=item.StudentID }) |
@Html.ActionLink("Details", "Details", new { id=item.StudentID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.StudentID })
</td></tr>
}Press CTRL+F5 執行。
點選Students 檢視。
慣例
EF的這些慣例,使得以上所寫程式碼不多:
- 實體類名得複數形式作為表名.
- 實體類的屬性名作為表的列名.
ID
或 classnameID
作為主鍵.
慣例可以不必遵守(如本文不用複數形式作為表名),如果使用慣例或者覆蓋慣例,請檢視後面的 Creating a More Complex Data Model 。更多資訊請檢視. Code First Conventions.
總結
使用 Entity Framework 和SQL Server Express 建立了一個簡單的web程式。隨後將學習如何完成基本的 CRUD (create, read, update, delete) 操作.