Add a new field to a Razor Page in ASP.NET Core

By Rick Anderson

In this section Entity Framework Code First Migrations is used to:

  • Add a new field to the model.
  • Migrate the new field schema change to the database.

When using EF Code First to automatically create a database, Code First:

  • Adds a table to the database to track whether the schema of the database is in sync with the model classes it was generated from.
  • If the model classes aren't in sync with the DB, EF throws an exception.

Automatic verification of schema/model in sync makes it easier to find inconsistent database/code issues.

Adding a Rating Property to the Movie Model

Open the Models/Movie.cs file and add a Rating property:

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.

Edit Pages/Movies/Index.cshtml, and add a Rating field:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">

    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Rating)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movie)
        {
            <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>
                    <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

Update the following pages:

  • Add the Rating field to the Delete and Details pages.
  • Update Create.cshtml with a Rating field.
  • Add the Rating field to the Edit Page.

The app won't work until the DB is updated to include the new field. If run now, the app throws a SqlException:

SqlException: Invalid column name 'Rating'.

This error is caused by the updated Movie model class being different than the schema of the Movie table of the database. (There's no Rating column in the database table.)

There are a few approaches to resolving the error:

  1. Have the Entity Framework automatically drop and re-create the database using the new model class schema. This approach is convenient early in the development cycle; it allows you to quickly evolve the model and database schema together. The downside is that you lose existing data in the database. Don't use this approach on a production database! Dropping the DB on schema changes and using an initializer to automatically seed the database with test data is often a productive way to develop an app.

  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. Use Code First Migrations to update the database schema.

For this tutorial, use Code First Migrations.

Update the SeedData class so that it provides a value for the new column. A sample change is shown below, but you'll want to make this change for each new Movie block.

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

See the completed SeedData.cs file.

Build the solution.

Add a migration for the rating field

From the Tools menu, select NuGet Package Manager > Package Manager Console. In the PMC, enter the following commands:

Add-Migration Rating
Update-Database

The Add-Migration command tells the framework to:

  • Compare the Movie model with the Movie DB schema.
  • Create code to migrate the DB schema to the new model.

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.

The Update-Database command tells the framework to apply the schema changes to the database.

If you delete all the records in the DB, the initializer will seed the DB and include the Rating field. You can do this with the delete links in the browser or from Sql Server Object Explorer (SSOX).

Another option is to delete the database and use migrations to re-create the database. To delete the database in SSOX:

  • Select the database in SSOX.

  • Right click on the database, and select Delete.

  • Check Close existing connections.

  • Select OK.

  • In the PMC, update the database:

    Update-Database
    

Run the app and verify you can create/edit/display movies with a Rating field. If the database isn't seeded, set a break point in the SeedData.Initialize method.

Additional resources

In this section Entity Framework Code First Migrations is used to:

  • Add a new field to the model.
  • Migrate the new field schema change to the database.

When using EF Code First to automatically create a database, Code First:

  • Adds a table to the database to track whether the schema of the database is in sync with the model classes it was generated from.
  • If the model classes aren't in sync with the DB, EF throws an exception.

Automatic verification of schema/model in sync makes it easier to find inconsistent database/code issues.

Adding a Rating Property to the Movie Model

Open the Models/Movie.cs file and add a Rating property:

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.

Edit Pages/Movies/Index.cshtml, and add a Rating field:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>

<form>
    <p>
        <select asp-for="MovieGenre" asp-items="Model.Genres">
            <option value="">All</option>
        </select>
        Title: <input type="text" asp-for="SearchString" />
        <input type="submit" value="Filter" />
    </p>
</form>

<table class="table">

    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Rating)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Movie)
        {
        <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>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
        }
    </tbody>
</table>

Update the following pages:

  • Add the Rating field to the Delete and Details pages.
  • Update Create.cshtml with a Rating field.
  • Add the Rating field to the Edit Page.

The app won't work until the DB is updated to include the new field. If run now, the app throws a SqlException:

SqlException: Invalid column name 'Rating'.

This error is caused by the updated Movie model class being different than the schema of the Movie table of the database. (There's no Rating column in the database table.)

There are a few approaches to resolving the error:

  1. Have the Entity Framework automatically drop and re-create the database using the new model class schema. This approach is convenient early in the development cycle; it allows you to quickly evolve the model and database schema together. The downside is that you lose existing data in the database. Don't use this approach on a production database! Dropping the DB on schema changes and using an initializer to automatically seed the database with test data is often a productive way to develop an app.

  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. Use Code First Migrations to update the database schema.

For this tutorial, use Code First Migrations.

Update the SeedData class so that it provides a value for the new column. A sample change is shown below, but you'll want to make this change for each new Movie block.

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

See the completed SeedData.cs file.

Build the solution.

Add a migration for the rating field

From the Tools menu, select NuGet Package Manager > Package Manager Console. In the PMC, enter the following commands:

Add-Migration Rating
Update-Database

The Add-Migration command tells the framework to:

  • Compare the Movie model with the Movie DB schema.
  • Create code to migrate the DB schema to the new model.

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.

The Update-Database command tells the framework to apply the schema changes to the database.

If you delete all the records in the DB, the initializer will seed the DB and include the Rating field. You can do this with the delete links in the browser or from Sql Server Object Explorer (SSOX).

Another option is to delete the database and use migrations to re-create the database. To delete the database in SSOX:

  • Select the database in SSOX.

  • Right click on the database, and select Delete.

  • Check Close existing connections.

  • Select OK.

  • In the PMC, update the database:

    Update-Database
    

Run the app and verify you can create/edit/display movies with a Rating field. If the database isn't seeded, set a break point in the SeedData.Initialize method.

Additional resources