在本节中,您将使用Entity Framework Code First来实现模型类上的操作。从而使得这些操作和变更,可以应用到数据库中。
默认情况下,就像您在之前的教程中所作的那样,使用 Entity Framework Code First自动创建一个数据库,Code First为数据库所添加的表,将帮助您跟踪数据库是否和从它生成的模型类是同步的。如果他们不是同步的,Entity Framework将抛出一个错误。这非常方便的在开发时就可以发现错误,否则您可能会在运行时才发现这个问题。
# 为对象模型的变更设置 Code First Migrations
从解决方案资源管理器中双击*Movies.mdf*,打开数据库工具, 在数据库工具 (数据库资源管理器、 服务器资源管理器或 SQL Server对象资源管理器),右键单击*Movies.mdf*f,并选择**删除**。
[![clip_image002](https://box.kancloud.cn/2016-01-02_568736b80464c.jpg "clip_image002")](http://images.cnitblog.com/blog/139239/201402/261139200361740.jpg)
Build应用程序,以确保没有任何编译错误。
从**工具**菜单上,单击**库包管理器**,然后点击**程序包管理器控制台**.
[![clip_image004](https://box.kancloud.cn/2016-01-02_568736b817c4e.jpg "clip_image004")](http://images.cnitblog.com/blog/139239/201402/261139216949512.jpg)
在**程序包管理器控制台窗口,在提示符**PM> 后输入:
Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext
[![clip_image006](https://box.kancloud.cn/2016-01-02_568736b82a2e5.gif "clip_image006")](http://images.cnitblog.com/blog/139239/201402/261139233871828.gif)
如上所示的**Enable-Migrations **命令,会在*Migrations*文件夹下创建一个*Configuration.cs* 文件。
[![clip_image008](https://box.kancloud.cn/2016-01-02_568736b8419ac.jpg "clip_image008")](http://images.cnitblog.com/blog/139239/201402/261139253843172.jpg)
在Visual Studio 里面打开*Configuration.cs* 文件. 用下面的代码替换Seed函数:
~~~
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
}
);
}
~~~
右键单击红色波浪线下Movie,并选择“解析(**Resolve**)”,然后单击“**using MvcMovie.Models**;
[![clip_image010](https://box.kancloud.cn/2016-01-02_568736b8565fe.jpg "clip_image010")](http://images.cnitblog.com/blog/139239/201402/261139302372220.jpg)
这样做会增加下面的语句:
using MvcMovie.Models;
Code First Migrations调用Seed的方法,每个迁移(**程序包管理器控制台**更新数据库),此方法用于updates数据(如果数据存在),或inserted数据。
在[AddOrUpdate](http://msdn.microsoft.com/en-us/library/system.data.entity.migrations.idbsetextensions.addorupdate(v=vs.103).aspx)方法在下面的代码执行一个的“upsert”操作:
~~~
context.Movies.AddOrUpdate(i => i.Title,
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "PG",
Price = 7.99M
}
~~~
因为[Seed](http://msdn.microsoft.com/en-us/library/hh829453(v=vs.103).aspx)方法与每个迁移同时运行时,故,你不能仅仅插入数据,因为当你正试图添加,可能已经完成了创建数据库后的第一次迁移。“[upsert](http://en.wikipedia.org/wiki/Upsert)”操作阻止错误的发生,如果你尝试插入一个已经存在的行,它覆盖任何数据更改,当你在测试应用程序的同时。你可能不希望这样的事情发生:在某些情况下,当您更改数据测试时,你希望你的变化后数据库同步更新。在这种情况下,你想要做一个有条件的插入操作:只有当它不存在的时候,插入一行。
传递给[AddOrUpdate](http://msdn.microsoft.com/en-us/library/system.data.entity.migrations.idbsetextensions.addorupdate(v=vs.103).aspx)的方法的第一个参数, 指定的属性来使用以检查是否已存在某行。对于您所提供的测试影片的数据,Title属性可以被用于此目的,因为每个标题在列表中是唯一:
context.Movies.AddOrUpdate(i => i.Title,
这个代码假设titiles属性是唯一的。如果手动添加一个重复的标题,你会得到下面的异常。
更多关于 [AddOrUpdate](http://msdn.microsoft.com/en-us/library/system.data.entity.migrations.idbsetextensions.addorupdate(v=vs.103).aspx) 方法的信息,请参见 [Take care with EF 4.3 AddOrUpdate Method](http://thedatafarm.com/blog/data-access/take-care-with-ef-4-3-addorupdate-method/)..
**按 CTRL-SHIFT-B 来Build工程。**(如果此次Build不成功,以下的步骤将会失败。)
下一步是创建一个DbMigration类,用于初始化数据库迁移。此迁移类将创建新的数据库,这也就是为什么在之前的步骤中你要删除`movie.mdf`文件。
在**软件包管理器控制台**窗口中,输入"add-migration Initial"命令来创建初始迁移。" Initial" 的名称是任意,是用于创建迁移文件的名称。
[![clip_image012](https://box.kancloud.cn/2016-01-02_568736b86a74d.jpg "clip_image012")](http://images.cnitblog.com/blog/139239/201402/261139321985320.jpg)
Code First Migrations将会在Migrations文件夹中创建另一个类文件 (文件名为:* {DateStamp}_Initial.cs* ),此类中包含的代码将创建数据库的Schema。迁移文件名使用时间戳作为前缀,以帮助用来排序和查找。查看*{DateStamp}_Initial.cs*文件,它包含了为电影数据库创建电影表的说明。当您更新数据库时,* {DateStamp}_Initial.cs*文件将会被运行并创建 DB 的Schema。然后**Seed**方法将运行,用来填充 DB 的测试数据。
在**软件包管理器控制台**中,输入命令" update-database ",创建数据库并运行**Seed**方法。
[![clip_image014](https://box.kancloud.cn/2016-01-02_568736b8829d9.jpg "clip_image014")](http://images.cnitblog.com/blog/139239/201402/261139342391679.jpg)
如果您收到表已经存在并且无法创建的错误,可能是因为您已经删除了数据库,并且在执行update-database之前,您运行了应用程序。在这种情况下,再次删除Movies.mdf文件,然后重试update-database命令。如果您仍遇到错误,删除Migration文件夹及其内容,然后从头开始重做。(即删除Movies.mdf文件,然后再进行Enable-Migrations)
运行该应用程序,然后浏览URL /Movies Seed数据显示如下:
[![clip_image016](https://box.kancloud.cn/2016-01-02_568736b896335.gif "clip_image016")](http://images.cnitblog.com/blog/139239/201402/261139383988882.gif)
# 为影片模型添加评级(Rating)属性
给现有的Movie类,添加新的Rating属性。打开Models\Movie.cs文件并添加如下Rating属性:
public string Rating { get; set; }
完整的`Movie`类如下:
~~~
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
public string Rating { get; set; }
}
~~~
Build 应用程序 **Build**>**Build Move**或CTRL-SHIFT-B.
因为你已经添加了新的字段,电影类的,你还需要Bind,所以这次新的属性将被包含。更新的绑定属性,Create和Edit动作方法, 包括Rating属性:
[Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")]
您还需要更新视图模板,以显示浏览器视图中创建和编辑新的评级(Rating)属性。
打开*\Views\Movies\Index.cshtml*文件,在**Price**列后面添加`<th>Rating</th>`的列头。然后添加一个`<td>`列来显示`@item.Rating`的值。下面是更新的*Index.cshtml*视图模板:
~~~
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("Index", "Movies", FormMethod.Get))
{
<p>
Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" />
</p>
}
</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>
~~~
下一步,打开*\Views\Movies\Create.cshtml*文件,并在form标签结束处的附近添加如下代码。您可以在创建新的电影时指定一个电影等级。
~~~
<div class="form-group">
@Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Rating, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Rating)
@Html.ValidationMessageFor(model => model.Rating)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
<div class="form-group">
@Html.LabelFor(model => model.Price, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Rating, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Rating)
@Html.ValidationMessageFor(model => model.Rating)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
~~~
现在,您已经更新应用程序代码以支持了新的`Rating`属性。
现在运行该应用程序,然后浏览 */Movies*的 URL。然而,当您这样做时,您将看到以下之一的错误信息:
[![clip_image018](https://box.kancloud.cn/2016-01-02_568736b8a5a17.gif "clip_image018")](http://images.cnitblog.com/blog/139239/201402/261139426642885.gif)
自从数据库创建后,备份的'MovieDBContext上下文模型已经改变。请考虑使用Code First Migrations更新数据库 (http://go.microsoft.com/fwlink/?LinkId=238269).
[![clip_image020](https://box.kancloud.cn/2016-01-02_568736b8bb8f8.gif "clip_image020")](http://images.cnitblog.com/blog/139239/201402/261139470229617.gif)
你看到这个错误,因为更新的的Movie模型类中比现在Movie现有数据库表的schema不同。 (在数据库表中没有Rating列。)
有几个解决错误的方法:
1. Entity Framework会自动删除并重新创建数据库根据新模型类schema。在开发周期的早期, 这种方式非常方便,当你正在做开发一个测试数据库,它可以让你快速演进模型和数据库schema。不足之处,你将失去现有的数据库中的数据 - 所以对生产数据库你不想使用这种方法! 通常是一个富有成效的办法,开发一个应用程序来初始化数据库的自动测试数据。更多关于Entity Framework database初始化的信息,请参阅Tom Dykstra's fantastic[ASP.NET MVC/Entity Framework tutorial](http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application).
2. 显式修改现有数据库的架构,以便它匹配的模型类。这种方法的优点是,你保持你的数据。可以使手动或通过建立数据库更改脚本实现它。
3. 使用Code First Migrations来更新数据库schema。
在本教程中,我们将使用Code First Migrations方法。
更新Seed 方法,以使它可以给新列提供一个值。 打开Migrations\Configuration.cs 文件,在每个Movie下添加Rating 字段.
~~~
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "PG",
Price = 7.99M
},
~~~
编译解决方案,打开**程序包管理器控制台窗体,输入如下命令:**
add-migration Rating
`add-migration`命令告诉migration framework,来检查当前电影模型与当前的影片 DB Schema并创建必要的代码以将数据库迁移到新的模型。AddRatingMig 是一个任意的文件名参数,用于命名migration文件。它将有助于使得迁移步骤成为一个有意义的名字。
当命令完成后,用Visual Studio 打开类文件,新继承自`DbMIgration` 类的定义,并在`Up` 方法中,您可以看到创建新列的代码:
~~~
public partial class AddRatingMig : DbMigration
{
public override void Up()
{
AddColumn("dbo.Movies", "Rating", c => c.String());
}
public override void Down()
{
DropColumn("dbo.Movies", "Rating");
}
}
~~~
Build解决方案,然后在 **程序包管理器控制台**窗口中输入"update-database"命令。
下面的图片显示了 **程序包管理器控制台**窗口的输出 (日期戳前面添加的评级会有所不同)
[![clip_image022](https://box.kancloud.cn/2016-01-02_568736b8d30dd.jpg "clip_image022")](http://images.cnitblog.com/blog/139239/201402/261139505036976.jpg)
重新运行应用程序,然后浏览 /Movies 的 URL。您可以看到新的评级字段。
[![clip_image024](https://box.kancloud.cn/2016-01-02_568736b8e78cf.jpg "clip_image024")](http://images.cnitblog.com/blog/139239/201402/261139534509479.jpg)
单击**CreateNew**链接来添加一部新电影。注意,请您可以为电影添加评级。
[![clip_image026](https://box.kancloud.cn/2016-01-02_568736b9075de.jpg "clip_image026")](http://images.cnitblog.com/blog/139239/201402/261139574897182.jpg)
单击**Create**。新的电影,包括评级,将显示在电影列表中:
[![clip_image028](https://box.kancloud.cn/2016-01-02_568736b918ce6.jpg "clip_image028")](http://images.cnitblog.com/blog/139239/201402/261140019513943.jpg)
该项目目前正在使用的迁移 (migrations),当你添加新的字段或更新数据库Schema, 你不需要删除数据库。在下一节中,我们将让更多的架构更改,并使用迁移来更新的数据库。
此外您也应该把`Rating` 字段添加到Edit、Details和Delete的视图模板中。
您可以再次在 **程序包管理器控制台**窗口中输入"update-database"命令,将不会有任何新的变化,因为数据库Schema 和模型类现在是匹配的。然而,运行“update-database”将运行再次Seed方法,如果你改变任何种子数据,更改都将丢失,因为Seed的方法upserts数据。你可以阅读更多关于Seed的方法Tom Dykstra's的流行的[ASP.NET MVC/Entity Framework tutorial](http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application).。
在本节中,您看到了如何修改模型对象并始终保持其和数据库Schema的同步。您还学习了使用填充示例数据来创建新数据库的例子,您可以反复尝试。这只是一个简单的介绍Code First,更完整的教程的请参阅[Creating an Entity Framework Data Model for an ASP.NET MVC Application](http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application)。接下来,让我们看看如何将丰富的验证逻辑添加到模型类,并对模型类执行一些强制的业务规则验证。
- 前言
- 入门介绍
- 添加一个控制器
- 添加一个视图
- 添加一个模型
- 从控制器访问数据模型
- 验证编辑方法和编辑视图
- 给电影表和模型添加新字段
- 给数据模型添加校验器
- 查询详细信息和删除记录
- 第三方控件Studio for ASP.NET Wijmo MVC4 工具应用
- ASP.NET MVC 5-开始MVC5之旅
- 控制器
- 视图
- 将数据从控制器传递给视图
- 添加一个模型
- 创建连接字符串(Connection String)并使用SQL Server LocalDB
- 从控制器访问数据模型
- 验证编辑方法(Edit method)和编辑视图(Edit view)
- 给电影表和模型添加新字段
- 给数据模型添加校验器
- 查询Details和Delete方法