這個簡單的Contoso大學Web應用演示了怎麼使用Entity Framework去創建一個ASP.NET MVC應用。這是爲虛構的Contoso大學構建的一個簡單網站應用。它包括了例如學生入檔,課程創建,教師分配等功能。
這個系列指導說明了開始構建一個Contoso大學簡單應用的相關步驟。您能下載完成後的應用或者跟着教程的步驟創建它。這個教程使用C#來展現例子,下載的示例中包含c#和Visual Basic。如果您有與這個教程沒有直接關聯的問題,可以把它們發送到ASP.NET Entity Framework forum或Entity Framework and LINQ to Entities forum。
這個系列教程假設您知道怎麼在Visual Studio中使用ASP.NET MVC,如果您不會使用,basic ASP.NET MVC Tutorial是一個很好的學習地方。如果您更喜歡使用ASP.NET Web Forms模式工作,可以看看Getting Started with the Entity Framework和Continuing with the Entity Framework教程。
在開始之前,請確保在您的計算機中有安裝下列必備軟件:
Contoso大學Web應用
您將會在這次教程中創建一個簡單的大學網站應用。
使用者能夠查看和更新學生,課程以及教師信息。下面是一些創建後的截圖。
站點的UI風格會跟自動生成的內置模板保持一致,以便這個教程能將重點聚焦到怎麼去使用Entity Framework上。
Entity Framework開發方法
正如下面圖表展示的,在Entity Framework中您有三種方式來開始工作:數據庫優先(Database First),模型優先(Model First)和代碼優先(Code First)。
數據庫優先(Database First)
如果您已經有一個數據庫,Entity Framework能自動的生成一個充斥着跟已存在數據庫中對象(如表和列)相一致的類和屬性的數據模型。數據庫的結構信息,您的數據模型和他們之間的映射都會存儲在以.edmx爲後綴的XML文件中。Visual Studio提供了Entity Framework圖形設計器,您能夠使用它顯示和編輯.edmx文件。在Web Forms系列教程中會用數據庫優先(Database First)的章節有Getting Started With the Entity Framework和Continuing With the Entity Framework。
模型優先(Model First)
如果您至今沒有一個數據庫,您能在Visual Studio中使用Entity Framework設計器創建一個模型,當模型完成了,設計器能生成DDL(data definition language)語句去創建一個數據庫。這個方法也使用了一個.edmx文件來存儲模型和映射信息。What's New in the Entity Framework 4教程包含了一個簡短的模型優先(Model First)的例子。
代碼優先(Code First)
不論您是否擁有一個已經存在的數據庫,您能使用代碼書寫自己的類和屬性跟數據庫中的表,列保持一致,並且在Entity Framework中使用這種方式不會生成一個.edmx文件。這就是爲什麼有時您會看到這種方法也被稱爲"code only",雖然官方名稱是代碼優先(Code First)。存儲結構和概念模型的映射代表了您的代碼根據一些約束和特殊的映射API來處理。如果您至今沒有一個數據庫,Entity Framework能自動爲您創建數據庫或當模型發生改變後,刪除並重新創建它。這個系列教程都會只用代碼優先(Code First)方法來開發。
這個數據操作API是爲基於DbContext類的代碼優先(Code First)而開發。這個API也能被數據庫優先(Database First)和模型優先(Model First)所使用。更多的信息,請參閱Entity Framework小組的博客When is Code First not code first?。
POCO (Plain Old CLR Objects)
默認的,當您使用數據庫優先(Database First)或模型優先(Model First)開發方法,數據模型中的實體類繼承自EntityObject類,這個基類提供了Entity Framework的功能。這意味着這些類不能隨意的使用,也就不能很舒服的符合領域驅動設計(domain-driven
design)的要求。Entity Framework的所有開發方法都能和POCO (plain old CLR objects)類一起工作,實質上意味着它們可以隨意使用,因爲它們不繼承自EntityObject
類。在教程中,您會使用POCO類。
在開始之前,請確保在您的計算機中有安裝下列必備軟件:
打開Visual Studio並創建一個新的ASP.NET MVC 3 Web Application模板項目,命名爲"ContosoUniversity":
在這個New ASP.NET MVC 3 Project對話框中選擇Internet Application模板和Razor視圖引擎,清空Create a unit test project選擇框,然後單擊OK。
設置站點風格
一些簡單的改變用與設置站點菜單,佈局和home頁面。
爲了設置Contoso大學菜單,在Views\Shared\_Layout.cshtml文件中,替換已經存在的h1頭文本和菜單鏈接,如下所示:
<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script> </head> <body> <div class="page"> <div id="header"> <div id="title"> <h1>Contoso University</h1> </div> <div id="logindisplay"> @Html.Partial("_LogOnPartial") </div> <div id="menucontainer"> <ul id="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> </div> </div> <div id="main"> @RenderBody() </div> <div id="footer"> </div> </div> </body> </html>
在Views\Home\Index.cshtml文件中,刪除h2標籤下的所有內容。
在Controllers\HomeController.cs文件中,修改成"Welcome to Contoso University!"來替換原有的 "Welcome to ASP.NET MVC!" 。
在Content\Site.css文件中,爲了使菜單標籤頁靠左,像下面一下修改樣式文件:
- 在定義的#main中,增加clear: both;,如下所示:
#main { clear: both; padding: 30px 30px 15px 30px; background-color: #fff; border-radius: 4px 0 0 0; -webkit-border-radius: 4px 0 0 0; -moz-border-radius: 4px 0 0 0; }
- 在定義的nav 和
#menucontainer
中,增加clear: both; float: left;,如下所示:nav, #menucontainer { margin-top: 40px; clear: both; float: left; }
啓動網站。您看到home頁面中的主菜單如下所示。
創建數據模型
接下來您將爲Contoso大學應用創建實體類,先從下面三個實體開始:
Student和Enrollment
實體是一對多的關係,並且,Course和Enrollment實體也是一對多的關係,換句話說,一個學生能註冊多門課程,並且一門課程能被多名學生註冊。
在下面的章節中,您將會爲每個實體創建相應的類。
注:如果您還沒有創建所有實體類,就嘗試編譯項目,這會導致編譯錯誤。
Student實體
在Models文件夾中,創建Student.cs文件,用下面的代碼來替換原有代碼:
using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Student { public int StudentID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
StudentID屬性將變成和類相一致的數據庫表中的主鍵列。默認,Entity Framework會把屬性名是ID或classname +ID的屬性作爲主鍵。
Enrollments是一個導航屬性,導航屬性把其他實體關聯到這個實體,一個Student實體的Enrollments屬性用來保存所有跟這個Student實體相關的Enrollment實體,換句話說,如果數據庫中有一個給定的Student行被關聯了兩個Enrollment行(Student主鍵值對應這個Enrollment表中StudentID外鍵列),那麼這個Student實體的Enrollments導航屬性將包含那兩個Enrollment實體。
導航屬性作爲virtual
被定義,以便讓Entity Framework給它們提供一種被稱作延遲加載的功能特性。(延遲加載會在之後說明,在這個系列教程的Reading
Related Data中)。如果一個導航屬性需要保存多個實體,那麼它的類型必須是實現了ICollection接口。
Enrollment實體
在Models文件夾中,創建Enrollment.cs文件,用下面的代碼來替換原有代碼:
using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } public decimal? Grade { get; set; } public virtual Course Course { get; set; } public virtual Student Student { get; set; } } }
在decimal類型的後面標註了一個?,表明Grade屬性是可空的。一個可空的Grade和一個爲0的Grade是不同的—空意味着一個Grade沒有被附過值,而0則意味着這個Grede被附加了0這個值。
這個StudentID是一個外鍵,並且與Student中的導航屬性保持一致,一個Enrollment實體和一個Student實體有所關聯,因此這個屬性僅能保存單個Student實體。(不像您之前看到的Student.Enrollments導航屬性,能保存多個Enrollment實體。)
這個CourseID屬性是一個外鍵,並且與Course中的導航屬性保持一致,一個Enrollment實體和一個Course實體有所關聯。
Course實體
在Models文件夾中,創建Course.cs文件,用下面的代碼來替換原有代碼:
using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
這個Enrollments是一個導航屬性,一個Course實體可以關聯到多個Enrollment實體。
創建數據庫上下文(Context)
這個用於爲一個給定的數據模型協調Entity Framework功能的總類是數據庫上下文(context)類。您創建的這個類派生自System.Data.Entity.DbContext類,在您的代碼中,您指定了把實體包含到數據模型中,您也能自定義Entity Framework的行爲。在這個項目的代碼中,這個類被命名爲SchoolContext。
創建一個DAL文件夾。在文件夾中創建一個新類,命名爲SchoolContext.cs,之後用下面的代碼來替換原有代碼:
using System; using System.Collections.Generic; using System.Data.Entity; using ContosoUniversity.Models; using System.Data.Entity.ModelConfiguration.Conventions; namespace ContosoUniversity.Models { public class SchoolContext : DbContext { public DbSet<Student> Students { get; set; } public DbSet<Enrollment> Enrollments { get; set; } public DbSet<Course> Courses { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } } }
代碼中爲每個實體集創建了一個DbSet屬性。在Entity Framework技術中,一個實體集與數據庫中的表保持一致並且一個實體與表中的行保持一致。
OnModelCreating方法中的語句阻止了表名被限制成複數。如果您不這麼做,生成的表名將會被命名爲Students,Courses
, 和Enrollments
來取代想要生成的表名Student,Course
,和Enrollment,
之所以這樣,是因爲開發者就表名是否使用複數沒有達成一致。這個教程使用了單數形式,但重點是您可以自己選擇使用哪種形式來命名。
(這個類在Models命名空間中,因爲在一些情況,代碼優先(Code First)假設實體類和上下文(context)類是在同一命名空間中。)
設置連接字符串
您不必創建一個連接字符串,如果沒有創建,Entity Framework將會爲您自動的創建一個SQL Server Express數據庫。然而,在這次教程中,您將會用到SQL Server Compact,因此您需要指定一個連接字符串。
打開project Web.config文件並且增加一個新的連接字符串到connectionStrings集合中,如下所示。(確保您更新的是項目根目錄下的Web.config文件,在Views子文件夾中也存在Web.config文件,這個文件您不需要更新。)
<add name="SchoolContext" connectionString="Data Source=|DataDirectory|School.sdf" providerName="System.Data.SqlServerCe.4.0"/>
默認,Entity Framework會尋找和您的上下文(context)類名稱相同的連接字符串。這個連接字符中,您指定了SQL Server Compact數據庫的名稱爲School.sdf,存放在App_Data文件夾下。
給數據庫初始化測試數據
當應用啓動時,Entity Framework能爲您自動的創建(刪除並重新創建)一個數據庫。您能在應用中指定每次都重新創建或當模型與已存在數據庫不同步的時候在重新創建。爲了測試數據,您也能寫一個類包含一個方法,當每次重新創建完數據庫以後,Entity Framework去自動的調用它。在這章節,您將會指定無論模型是否發生改變,數據庫都會被刪並重新創建。
在DAL文件夾中,創建一個新類命名爲SchoolInitializer.cs,用下面的代碼來替換原有代碼,當需要和加載測試數據到一個新的數據庫時引發數據庫的重新創建。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Entity; using ContosoUniversity.Models; namespace ContosoUniversity.DAL { public class SchoolInitializer : DropCreateDatabaseIfModelChanges<SchoolContext> { protected override void Seed(SchoolContext context) { var students = new List<Student> { new Student { FirstMidName = "Carson", LastName = "Alexander", EnrollmentDate = DateTime.Parse("2005-09-01") }, new Student { FirstMidName = "Meredith", LastName = "Alonso", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Arturo", LastName = "Anand", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Gytis", LastName = "Barzdukas", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Yan", LastName = "Li", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Peggy", LastName = "Justice", EnrollmentDate = DateTime.Parse("2001-09-01") }, new Student { FirstMidName = "Laura", LastName = "Norman", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Nino", LastName = "Olivetto", EnrollmentDate = DateTime.Parse("2005-09-01") } }; students.ForEach(s => context.Students.Add(s)); context.SaveChanges(); var courses = new List<Course> { new Course { Title = "Chemistry", Credits = 3, }, new Course { Title = "Microeconomics", Credits = 3, }, new Course { Title = "Macroeconomics", Credits = 3, }, new Course { Title = "Calculus", Credits = 4, }, new Course { Title = "Trigonometry", Credits = 4, }, new Course { Title = "Composition", Credits = 3, }, new Course { Title = "Literature", Credits = 4, } }; courses.ForEach(s => context.Courses.Add(s)); context.SaveChanges(); var enrollments = new List<Enrollment> { new Enrollment { StudentID = 1, CourseID = 1, Grade = 1 }, new Enrollment { StudentID = 1, CourseID = 2, Grade = 3 }, new Enrollment { StudentID = 1, CourseID = 3, Grade = 1 }, new Enrollment { StudentID = 2, CourseID = 4, Grade = 2 }, new Enrollment { StudentID = 2, CourseID = 5, Grade = 4 }, new Enrollment { StudentID = 2, CourseID = 6, Grade = 4 }, new Enrollment { StudentID = 3, CourseID = 1 }, new Enrollment { StudentID = 4, CourseID = 1, }, new Enrollment { StudentID = 4, CourseID = 2, Grade = 4 }, new Enrollment { StudentID = 5, CourseID = 3, Grade = 3 }, new Enrollment { StudentID = 6, CourseID = 4 }, new Enrollment { StudentID = 7, CourseID = 5, Grade = 2 }, }; enrollments.ForEach(s => context.Enrollments.Add(s)); context.SaveChanges(); } } }
這個Seed方法把數據庫上下文(context)對象當做輸入參數,並且方法中的代碼使用這個對象把新的實體添加到數據庫中。代碼爲每個實體類型創建了一個新的實體集合,之後把這個改變保存到數據庫中。把每個實體都添加以後,調用SaveChanges方法不是必須的,在這裏,我們還是調用了,當正在將數據寫入到數據庫中,如果引發了一個異常,這麼做可以幫助您定位到這個問題源。
在Global.asax.cs文件中做出如下改變,這樣當應用啓動時,會引發初始化代碼:
增加using
聲明:using System.Data.Entity; using ContosoUniversity.Models; using ContosoUniversity.DAL;
- 在Application_Start方法中,調用一個Entity Framework方法來跑起數據庫初始化類:
Database.SetInitializer<SchoolContext>(new SchoolInitializer());
當您在第一次啓動應用時,Entity Framework將數據庫和模型進行對比,如果有所不同,應用將會刪除並重新創建數據庫。
注:當在生產Web服務器中部署這個應用,您必須移除生成數據庫測試數據的代碼。
現在,您將創建一個web頁面來顯示數據,並且請求數據的過程將會自動的觸發數據庫創建操作。您將創建一個新的控制器controller),但在做這之前,構建有效的模型(model)和上下文(context)類用於MVC自動生成的控制器(controller)。
創建Student控制器(Controller)
要創建Student控制器(controller),在Solution Explorer的Controllers文件夾上右擊,選擇Add,然後點擊Controller,在彈出的Add Controller對話框中,按照下面的選項進行選擇,最後單擊Add:
- Controller name: StudentController.
- Template: Controller with read/write actions and views, using Entity Framework. (默認)
- Model class: Student (ContosoUniversity.Models). (如果在下拉框中沒有看到這個選項,重新編譯項目,然後在嘗試一下。)
- Data context class: SchoolContext (ContosoUniversity.Models).
- Views: Razor (CSHTML). (The default.)
打開Controllers\StudentController.cs文件,您會看到類中已經創建了數據庫上下文(context)對象的實例:
private SchoolContext db = new SchoolContext();
這個Index的action方法是從數據庫上下文(context)實例的Students屬性中,獲得學生列表:
public ViewResult Index() { return View(db.Students.ToList()); }
自動生成工具也創建了一個Student視圖(views),進入到Index視圖(view)來自定義默認的標題和列,打開Views\Student\Index.cshtml文件並且用下面的代碼來替換原有代碼:
@model IEnumerable<ContosoUniversity.Models.Student> @{ ViewBag.Title = "Students"; } <h2>Students</h2> <p> @Html.ActionLink("Create New", "Create") </p> <table> <tr> <th></th> <th>Last Name</th> <th>First Name</th> <th>Enrollment Date</th> </tr> @foreach (var item in Model) { <tr> <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> <td> @Html.DisplayFor(modelItem => item.LastName) </td> <td> @Html.DisplayFor(modelItem => item.FirstMidName) </td> <td> @Html.DisplayFor(modelItem => item.EnrollmentDate) </td> </tr> } </table>
啓動網站,點擊Students標籤頁,之後您會看到學生列表。
關閉瀏覽器。在Solution Explorer中,選擇ContosoUniversity項目(確保項目而不是解決方案被選中)。單擊Show all Files,單擊Refresh,之後展開App_Data文件夾,會看到School.sdf文件。
Double-click雙擊 School.sdf 打開Server Explorer。然後展開Tables文件夾,會看到所有的表已經創建到數據庫中。
注:當雙擊School.sdf時,如果發生錯誤,請確保您有安裝Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0。(在頂部的必備軟件中,有軟件下載鏈接)。如果您剛剛安裝了這個工具,必須要重啓Visual Studio才能生效。
每個實體集都會生成一個表,以及外加一個額外的表EdmMetadata,這個表用於檢查數據庫與模型的同步。
右擊其中的一個表,選擇Show Table Data,會看到根據SchoolInitializer類初始化的數據已經加載到表中。
當你執行完操作,關閉連接。(如果沒有關閉連接,在您下載啓動項目時,可能會獲得一個錯誤。)
約定
由於使用了約定和假設,您在Entity Framework中使用極少的井然有序的代碼就可以創建一個完備的數據庫,下面是一些需要注意的地方:
- 實體類名稱的複數形式會作爲表的名稱使用。
- 實體屬性名稱常常用作列名。
- Entity Framework會把屬性名是ID或classname +ID的屬性作爲主鍵。
- Entity Framework會尋找和您上下文(context)類名稱相同的連接字符串去連接到數據庫 (在這裏是
SchoolContext
).
正如您之前看到的,約定是能夠被重寫的(例如,您指定了表名不必使用複數形式),在之後系列教程的Creating a More Complex Data Model中,您將學會更多的約定和怎麼去重寫它們。
您現在使用Entity Framework和SQL Server Compact創建了一個簡單的應用去存儲和顯示數據,在接下來的教程中,您將會學習到怎麼去執行基於CRUD(create, read, update, delete)的操作。