添加新字段
作者: 里克·安德森
注意
本教程的更新版本是使用最新版本的 Visual Studio 提供的。 新教程使用 ASP.NET Core MVC,这为本教程提供了 许多 改进。
本教程介绍具有控制器和视图的 ASP.NET Core MVC。 Razor Pages 是 ASP.NET Core 中的一种新替代方法,它是一种基于页面的编程模型,使生成 Web UI 更加轻松高效。 建议先尝试 Razor 页面教程,再使用 MVC 版本。 Razor 页面教程:
- 易于关注。
- 涵盖更多功能。
- 是新应用开发的首选方法。
在本部分中,你将使用Entity Framework Code First 迁移将一些更改迁移到模型类,以便将更改应用于数据库。
默认情况下,使用 Entity Framework Code First 自动创建数据库时,如本教程前面所述,Code First 向数据库添加一个表,以帮助跟踪数据库的架构是否与生成的模型类同步。 如果它们未同步,则 Entity Framework 将引发错误。 这样就可以更轻松地在开发时跟踪你可能只在运行时发现(通过模糊错误)的问题。
为模型更改设置Code First 迁移
导航到解决方案资源管理器。 右键单击Movies.mdf文件,然后选择“删除”以删除电影数据库。 如果未看到Movies.mdf文件,请单击红色轮廓中显示的“显示所有文件”图标。
生成应用程序,以确保没有任何错误。
在“工具”菜单中,单击“NuGet 包管理器”,然后单击“包管理器控制台”。
在提示符处的“程序包管理器控制台”窗口中PM>
Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext
Enable-Migrations 命令(如上所示)在新迁移文件夹中创建Configuration.cs文件。
Visual Studio 将打开 Configuration.cs 文件。 将 Seed
Configuration.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
}
);
}
将鼠标悬停在红色波浪线下方Movie
并单击,然后单击“使用 MvcMovie.Models”;Show Potential Fixes
这样做会添加以下 using 语句:
using MvcMovie.Models;
注意
Code First 迁移每次迁移(即在 程序包管理器 控制台中调用 update-database)后调用Seed
该方法,此方法更新已插入的行,或者如果它们尚不存在,则将其插入。
以下代码中的 AddOrUpdate 方法执行“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 方法在每次迁移时都运行,因此不能只插入数据,因为尝试添加的行在创建数据库的第一次迁移之后就已存在。 如果尝试插入已存在的行,则“upsert”操作会阻止发生错误,但它会替代测试应用程序时对数据所做的任何更改。 对于某些表中的测试数据,你可能不希望发生这种情况:在某些情况下,在测试时更改数据时,希望更改在数据库更新后保留。 在这种情况下,需要执行条件插入操作:仅当行尚不存在时插入行。
传递给 AddOrUpdate 方法的第一个参数指定用于检查是否存在行的属性。 对于您提供的测试电影数据,该属性可用于此目的, Title
因为列表中的每个标题都是唯一的:
context.Movies.AddOrUpdate(i => i.Title,
此代码假定游戏是唯一的。 如果手动添加重复的标题,下次执行迁移时,将看到以下异常。
序列包含多个元素
有关 AddOrUpdate 方法的详细信息,请参阅 EF 4.3 AddOrUpdate 方法。
按 Ctrl-Shift-B 生成项目。(如果此时未生成,以下步骤将失败。
下一步是为初始迁移创建 DbMigration
类。 此迁移会创建一个新数据库,这就是在上一步中删除 movie.mdf 文件的原因。
在“程序包管理器控制台”窗口中,输入用于创建初始迁移的命令add-migration Initial
。 名称“Initial”是任意名称,用于命名创建的迁移文件。
Code First 迁移在 Movies
表的说明。 在以下说明中更新数据库时,此 {DateStamp}_Initial.cs 文件将运行并创建 DB 架构。 然后, Seed 方法将运行以使用测试数据填充 DB。
在程序包管理器控制台中,输入用于创建数据库并运行该方法的Seed
命令update-database
。
如果收到一个错误,指出表已存在且无法创建,则可能是因为在删除数据库后以及执行 update-database
之前运行了应用程序。 在这种情况下,请再次删除 Movies.mdf 文件,然后重试 update-database
该命令。 如果仍收到错误,请删除迁移文件夹和内容,然后从此页面顶部的说明开始(即删除 Movies.mdf 文件,然后继续启用迁移)。 如果仍然收到错误,请打开 SQL Server 对象资源管理器并从列表中删除数据库。 如果收到指示“无法将文件.mdf作为数据库附加”的错误,请在 web.config 文件中删除初始目录属性作为连接字符串的一部分。
运行应用程序并导航到 /Movies URL。 将显示种子数据。
向电影模型添加分级属性
首先向 Rating
现有 Movie
类添加新属性。 打开 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; }
}
生成应用程序(Ctrl+Shift+B)。
由于已向类添加新字段 Movie
,因此还需要更新绑定 允许列表 ,以便包含此新属性。 更新 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 文件,并添加 Rating
包含以下突出显示标记的字段。 这会呈现一个文本框,以便在创建新电影时指定分级。
<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, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Price, "", new { @class = "text-danger" })
</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, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Rating, "", new { @class = "text-danger" })
</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。 不过,执行此操作时,会看到以下错误之一:
自创建数据库以来,支持“MovieDBContext”上下文的模型发生了更改。 请考虑使用Code First 迁移来更新数据库(https://go.microsoft.com/fwlink/?LinkId=238269)。
你会看到此错误,因为应用程序中更新 Movie
的模型类现在不同于现有数据库的表架构 Movie
。 (数据库表中没有 Rating
列。)
可通过几种方法解决此错误:
- 让 Entity Framework 自动丢弃,并基于新的模型类架构重新创建数据库。 在测试数据库上进行开发时,此方法在开发周期早期很方便;通过它可以一起快速改进模型和数据库架构。 不过,缺点是,在数据库中丢失现有数据,因此 你不想 在生产数据库中使用此方法! 使用初始值设定项,以使用测试数据自动设定数据库种子,这通常是开发应用程序的有效方式。 有关 Entity Framework 数据库初始值设定项的详细信息,请参阅 ASP.NET MVC/Entity Framework 教程。
- 对现有数据库架构进行显式修改,使它与模型类相匹配。 此方法的优点是可以保留数据。 可以手动或通过创建数据库更改脚本进行此更改。
- 使用 Code First 迁移更新数据库架构。
本教程使用 Code First 迁移。
更新 Seed 方法,以便为新列提供值。 打开 Migrations\Configuration.cs 文件,并将“评级”字段添加到每个 Movie 对象。
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
命令告知迁移框架使用当前电影 DB 架构检查当前电影模型,并创建必要的代码将 DB 迁移到新模型。 名称分级是任意的,用于命名迁移文件。 对迁移步骤使用有意义的名称很有帮助。
此命令完成后,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");
}
}
生成解决方案,然后在程序包管理器控制台窗口中输入update-database
命令。
下图显示了程序包管理器控制台窗口中的输出(日期戳前面分级将有所不同)。
重新运行应用程序并导航到 /Movies URL。 可以看到新的“评分”字段。
单击“新建”链接添加新电影。 请注意,可以添加分级。
单击 “创建” 。 新电影,包括评级,现在显示在电影列表:
现在,项目正在使用迁移,在添加新字段或更新架构时,无需删除数据库。 在下一部分中,我们将进行更多架构更改,并使用迁移来更新数据库。
还应将 Rating
字段添加到“编辑”、“详细信息”和“删除”视图模板。
你可以再次 程序包管理器在“控制台”窗口中输入“update-database”命令,并且不会运行任何迁移代码,因为架构与模型匹配。 但是,运行“update-database”将再次运行 Seed
该方法,如果更改了任何种子数据,则更改将丢失,因为 Seed
该方法会更新插入数据。 可以在 Tom Dykstra 的热门 ASP.NET MVC/Entity Framework 教程中详细了解该方法Seed
。
本部分介绍了如何修改模型对象,并使数据库与更改保持同步。 你还了解了使用示例数据填充新创建的数据库的方法,以便可以试用方案。 这只是 Code First 的快速简介,请参阅 为 ASP.NET MVC 应用程序 创建实体框架数据模型,获取有关主题的更完整的教程。 接下来,让我们看看如何向模型类添加更丰富的验证逻辑,以及如何强制实施一些业务规则。