MVC 使用EF Code First資料遷移之新增欄位
預設情況下,當我們使用Entity Framework Code First 自動建立一個數據庫,像我們之前教程中講的那樣,Code First 新增一個table幫我們跟蹤資料庫結構是否與模型類同步。如果不同步,Entity Framework 將丟擲一個錯誤,這樣更方便我們在開發的時候發現問題,否則只能在執行時通過晦澀的錯誤來查找了。
為模型更改設定 Code First 資料遷移
在解決方案資源管理器中,刪除自動建立的 Movies.mdf 檔案。
在工具選單中,選擇“庫程式包管理器”>“程式包管理器控制檯”:
圖1:開啟“程式包管理器控制檯”選單項
在“程式包管理器控制檯”視窗中輸入:Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext
圖2:執行命令
Enable-Migrations 命令建立了一個Migrations資料夾和Configuration.cs檔案。
圖3:新新增的檔案
開啟 Configuration.cs 檔案,使用以下程式碼替換 Seed 方法:
程式碼清單1:Seed 方法 - Configruation.cs
protected override void Seed(MvcMovie.Models.MovieDBContext context) { context.Movies.AddOrUpdate(i => i.Title, new Movie{ Title = "When Harry Met Sally", ReleaseDate = DateTime.Parse("1989-1-11"), Genre = "Romantic Comedy", Price = 7.99M }, new Movie { Title = "Ghostbusters ", ReleaseDate = DateTime.Parse("1984-3-13"), Genre ="Comedy", Price = 8.99M }, new Movie { Title = "Ghostbusters 2", ReleaseDate = DateTime.Parse("1986-2-23"), Genre = "Comedy", Price = 9.99M }, new Movie { Title = "Rio Bravo", ReleaseDate = DateTime.Parse("1959-4-15"), Genre = "Western", Price = 3.99M } ); }
使用這段程式碼的時候,需要新增 using MvcMovie.Models 的引用。
Code First 資料遷移在每次遷移(在程式包管理器控制檯中呼叫 update-database)的時候都會呼叫Seed方法。
在進行下一步之前,先編譯解決方案,否則下一步會出錯誤。
下一步,為初始化遷移建立一個 DbMigration 類。這次遷移建立一個新資料庫,這也是我們為什麼要刪除之前的資料庫的原因。
在“程式包管理器控制檯”視窗,輸入命令 add-migration Initial
建立初始化遷移。名稱“Initial”是隨意命名的,它用來命名建立好的遷移檔案。
圖4:建立初始化遷移
Code First Migrations 建立在Migrations資料夾中建立了一個檔案(檔名是 {DateStamp}_Initial.cs ),這個類包含了建立資料庫結構的程式碼。遷移檔案的檔名以DateStamp開頭是為了更好的排序,開啟 {DateStamp}_Initial.cs 檔案,它包含了為資料庫MovieDB建立Movies表的指令。當你使用下面的命令更新資料庫時,{DateStamp}_Initial.cs 檔案將會執行並建立資料庫結構,然後將執行 Seed 方法將測試資料插入資料庫中。
在“程式包管理器控制檯”中輸入命令 update-database
:
圖5:執行更新資料庫命令
執行應用程式,瀏覽/Movies 地址,我們在Seed方法中新增的資料如下:
圖6:瀏覽程式
為Movie模型新增Rating欄位
上面的內容一直在介紹如何進行資料遷移,現在開始為Movie類新增Rating欄位,開啟Movie.cs 檔案,為它新增一個Rating欄位,新增後的程式碼如下:
程式碼清單2:新增Rating欄位後的Movie類
public class Movie { public int ID { get; set; } public string Title { get; set; } [Display(Name = "Release Date")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } public string Genre { get; set; } public decimal Price { get; set; } public string Rating { get; set; } }
編譯解決方案。
現在我們已經更新了Movie類,你還需要修改\Views\Movies\Index.cshtml 和 \Views\Movies\Create.cshtml 檢視。修改後的程式碼如下:
程式碼清單3:修改後的Index.cshtml
@model IEnumerable<MvcMovie.Models.Movie> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("Create New", "Create") </p> @using (Html.BeginForm("Index", "Movies", FormMethod.Get)) { <p> Genre: @Html.DropDownList("movieGenre", "All") Title: @Html.TextBox("SearchString") <br /> <input type="submit" value="Filter" /> </p> } <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Genre) </th> <th> @Html.DisplayNameFor(model => model.Price) </th> <th> @Html.DisplayNameFor(model => model.Rating) </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.ReleaseDate) </td> <td> @Html.DisplayFor(modelItem => item.Genre) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> @Html.DisplayFor(modelItem => item.Rating) </td> <td> @Html.ActionLink("Edit", "Edit", new { id = item.ID }) | @Html.ActionLink("Details", "Details", new { id = item.ID }) | @Html.ActionLink("Delete", "Delete", new { id = item.ID }) </td> </tr> } </table>
程式碼清單3:修改後的Create.cshtml
@model MvcMovie.Models.Movie @{ ViewBag.Title = "Create"; } <h2>Create</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <fieldset class="form-horizontal"> <legend>Movie</legend> <div class="control-group"> @Html.LabelFor(model => model.Title, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Title) @Html.ValidationMessageFor(model => model.Title, null, new { @class = "help-inline" }) </div> </div> <div class="control-group"> @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.ReleaseDate) @Html.ValidationMessageFor(model => model.ReleaseDate, null, new { @class = "help-inline" }) </div> </div> <div class="control-group"> @Html.LabelFor(model => model.Genre, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Genre) @Html.ValidationMessageFor(model => model.Genre, null, new { @class = "help-inline" }) </div> </div> <div class="control-group"> @Html.LabelFor(model => model.Price, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Price) @Html.ValidationMessageFor(model => model.Price, null, new { @class = "help-inline" }) </div> </div> <div class="control-group"> @Html.LabelFor(model => model.Rating, new { @class = "control-label" }) <div class="controls"> @Html.EditorFor(model => model.Rating) @Html.ValidationMessageFor(model => model.Rating, null, new { @class = "help-inline" }) </div> </div> <div class="form-actions no-color"> <input type="submit" value="Create" class="btn" /> </div> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
現在我們已經在程式中為Rating欄位做成了修改。再次執行程式,瀏覽/movies 地址,這時我們會得到一個錯誤:
圖7:錯誤頁面
出現這個錯誤的原因是Movie模型類發生了變化,而與它對應的資料表 Movie 中並不存在Rating欄位。
There are a few approaches to resolving the error:
解決這個問題有以下幾種途徑:
- 讓Entity Framework自動刪除並根據新的模型自動建立資料庫。這種方式在早起開發過程中的測試資料庫中非常方便,它可以快速的修改模型和資料庫結構。另一方面,這樣做將會使你丟失已有的資料,因此這種方式不能用在生產環境的資料庫中。
- 在資料庫中加上Rating欄位,使資料庫和Model類的結構相同。這種方式的優點是能夠保留資料,你可以手動修改或使用資料庫指令碼修改。
- 使用Code First 遷移來升級資料庫結構。
在本教程中,我們使用Code First 遷移。
更新Seed 方法,使它為Rating欄位提供一個值。開啟 Migrations\Configuration.cs 檔案,為每一個Movie物件的Rating欄位賦值。
程式碼清單4:修改後的Seek方法
protected override void Seed(MvcMovie.Models.MovieDBContext context) { context.Movies.AddOrUpdate(i => i.Title, new Movie { Title = "龍門飛甲", ReleaseDate = DateTime.Parse("2012-1-11"), Genre = "動作", Price = 30M, Rating = "優" }, new Movie { Title = "冰河世紀", ReleaseDate = DateTime.Parse("2011-3-1"), Genre = "動漫", Price = 65M, Rating = "良" }, new Movie { Title = "中國合夥人", ReleaseDate = DateTime.Parse("2013-6-18"), Genre = "勵志", Price = 70M, Rating = "良" } ); }
重新編譯解決方案,然後開啟“程式包管理器控制檯”,執行命令:add-migration Rating
add-migration 命令告訴遷移程式去檢查當前的Movie模型與當前資料庫之間的差異,建立遷移資料庫到最新模型的程式碼。名稱 Rating 是可以隨便命名的,此處用來命名遷移檔案。
當命令執行完成之後,Visual Studio 會開啟剛剛新增的繼承自 DbMigration 的類檔案,檔案中有兩個方法 Up和Down,分別用來升級和降級資料庫。在Up方法中我們可以看到為資料庫新增列的程式碼,而Down方法中的程式碼則是刪除Rating列。
程式碼清單5:Rating類
public partial class Rating : DbMigration { public override void Up() { AddColumn("dbo.Movies", "Rating", c => c.String()); } public override void Down() { DropColumn("dbo.Movies", "Rating"); } }
編譯解決方案,然後執行命令 update-database
。“程式包管理器控制檯”視窗的輸出如下圖(Rating前的時間戳可能不盡相同):
圖8:更新資料庫
重新整理我們出錯的頁面,你能看到已經加入了Rating欄位:
圖9:加入了Rating欄位的介面
點選“Create New”連結試著新增一個電影資訊,不要忘記為Rating欄位賦值。
圖10:新增頁面
輸入完成之後點選“Create”按鈕,儲存電影資訊。
圖11:儲存後的列表
你還需要在Edit、Details和Delete檢視中新增Rating欄位。
如果你再次執行 "update-database" 命令,將不會做出任何修改,因為資料庫結構和模型的結構已經相同了。
現在,通過專案中使用資料遷移,我們在新增欄位或更新模型結構的時候不用再刪除資料庫了。在下一節中,我們將對結構做出更多的更改,並使用資料遷移來更新資料庫。