第 8 部分,将新字段添加到 ASP.NET Core MVC 应用Part 8, add a new field to an ASP.NET Core MVC app

作者:Rick AndersonBy Rick Anderson

在此部分中,Entity Framework Code First 迁移用于:In this section Entity Framework Code First Migrations is used to:

  • 将新字段添加到模型。Add a new field to the model.
  • 将新字段迁移到数据库。Migrate the new field to the database.

使用 EF Code First 自动创建数据库时,Code First 将:When EF Code First is used to automatically create a database, Code First:

  • 将表添加到数据库,以跟踪数据库的架构。Adds a table to the database to track the schema of the database.
  • 验证数据库与生成它的模型类是否同步。Verifies the database is in sync with the model classes it was generated from. 如果它们不同步,EF 则会引发异常。If they aren't in sync, EF throws an exception. 这使查找不一致的数据库/代码问题变得更加轻松。This makes it easier to find inconsistent database/code issues.

向电影模型添加分级属性Add a Rating Property to the Movie Model

Rating 属性添加到 Models/Movie.cs:Add a Rating property to Models/Movie.cs:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models
{
    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; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
        public string Rating { get; set; }
    }
}

生成应用Build the app

Ctrl+Shift+BCtrl+Shift+B

因为已经添加新字段到 Movie 类,所以需要更新属性绑定列表,将此新属性纳入其中。Because you've added a new field to the Movie class, you need to update the property binding list so this new property will be included. 在 MoviesController.cs 中,更新 CreateEdit 操作方法的 [Bind] 属性,以包括 Rating 属性:In MoviesController.cs, update the [Bind] attribute for both the Create and Edit action methods to include the Rating property:

[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")]

更新视图模板以在浏览器视图中显示、创建和编辑新的 Rating 属性。Update the view templates in order to display, create, and edit the new Rating property in the browser view.

编辑 /Views/Movies/Index.cshtml 文件并添加 Rating 字段:Edit the /Views/Movies/Index.cshtml file and add a Rating field:

<thead>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Title)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Genre)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Price)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Rating)
        </th>
        <th></th>
    </tr>
</thead>
<tbody>
    @foreach (var item in Model.Movies)
    {
        <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>
<thead>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Title)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].ReleaseDate)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Genre)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Price)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Movies[0].Rating)
        </th>
        <th></th>
    </tr>
</thead>
<tbody>
    @foreach (var item in Model.Movies)
    {
        <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>

使用 Rating 字段更新 /Views/Movies/Create.cshtml。Update the /Views/Movies/Create.cshtml with a Rating field.

可以复制/粘贴之前的“窗体组”,并让 intelliSense 帮助更新字段。You can copy/paste the previous "form group" and let intelliSense help you update the fields. IntelliSense 适用于标记帮助程序IntelliSense works with Tag Helpers.

开发人员已在视图的第二个标签元素中键入字母 R 用作 asp-for 的特性值。

更新剩余模板。Update the remaining templates.

更新 SeedData 类,使它提供新列的值。Update the SeedData class so that it provides a value for the new column. 示例更改如下所示,但可能需要对每个 new Movie 做出此更改。A sample change is shown below, but you'll want to make this change for each new Movie.

new Movie
{
    Title = "When Harry Met Sally",
    ReleaseDate = DateTime.Parse("1989-1-11"),
    Genre = "Romantic Comedy",
    Rating = "R",
    Price = 7.99M
},

在 DB 更新为包括新字段之前,应用将不会正常工作。The app won't work until the DB is updated to include the new field. 如果它现在运行,将引发以下 SqlExceptionIf it's run now, the following SqlException is thrown:

SqlException: Invalid column name 'Rating'.

发生此错误是因为更新的 Movie 模型类与现有数据库的 Movie 表架构不同。This error occurs because the updated Movie model class is different than the schema of the Movie table of the existing database. (数据库表中没有 Rating 列。)(There's no Rating column in the database table.)

可通过几种方法解决此错误:There are a few approaches to resolving the error:

  1. 让 Entity Framework 自动丢弃,并基于新的模型类架构重新创建数据库。Have the Entity Framework automatically drop and re-create the database based on the new model class schema. 在测试数据库上进行开发时,此方法在开发周期早期很方便;通过它可以一起快速改进模型和数据库架构。This approach is very convenient early in the development cycle when you're doing active development on a test database; it allows you to quickly evolve the model and database schema together. 但其缺点是会丢失数据库中的现有数据 - 因此请勿对生产数据库使用此方法!The downside, though, is that you lose existing data in the database — so you don't want to use this approach on a production database! 使用初始值设定项,以使用测试数据自动设定数据库种子,这通常是开发应用程序的有效方式。Using an initializer to automatically seed a database with test data is often a productive way to develop an application. 对于早期开发和使用 SQLite 的情况,这是一个不错的方法。This is a good approach for early development and when using SQLite.

  2. 对现有数据库架构进行显式修改,使它与模型类相匹配。Explicitly modify the schema of the existing database so that it matches the model classes. 此方法的优点是可以保留数据。The advantage of this approach is that you keep your data. 可以手动或通过创建数据库更改脚本进行此更改。You can make this change either manually or by creating a database change script.

  3. 使用 Code First 迁移更新数据库架构。Use Code First Migrations to update the database schema.

对于本教程,请使用 Code First 迁移。For this tutorial, Code First Migrations is used.

从“工具”菜单中,选择“NuGet 包管理器”>“包管理器控制台”。 From the Tools menu, select NuGet Package Manager > Package Manager Console.

PMC 菜单

在 PMC 中,输入以下命令:In the PMC, enter the following commands:

Add-Migration Rating
Update-Database

Add-Migration 命令会通知迁移框架使用当前 Movie DB 架构检查当前 Movie 模型,并创建必要的代码,将 DB 迁移到新模型。The Add-Migration command tells the migration framework to examine the current Movie model with the current Movie DB schema and create the necessary code to migrate the DB to the new model.

名称“Rating”是任意的,用于对迁移文件进行命名。The name "Rating" is arbitrary and is used to name the migration file. 为迁移文件使用有意义的名称是有帮助的。It's helpful to use a meaningful name for the migration file.

如果删除 DB 中的所有记录,初始化方法会设定 DB 种子,并将包括 Rating 字段。If all the records in the DB are deleted, the initialize method will seed the DB and include the Rating field.

运行应用,并验证是否可以创建、编辑和显示具有 Rating 字段的电影。Run the app and verify you can create, edit, and display movies with a Rating field.