Adding A New Field

This tutorial is the ninth in a series of a Visual Basic versions of the Introduction to ASP.NET MVC 5 tutorials published on the www.asp.net site. The original series, produced by Scott Guthrie (twitter @scottgu ), Scott Hanselman (twitter: @shanselman ), and Rick Anderson ( @RickAndMSFT ) was written using the C# language. My versions keep as close to the originals as possible, changing only the coding language. The narrative text is largely unchanged from the original and is used with permission from Microsoft.

This tutorial series will teach you the basics of building an ASP.NET MVC 5 Web application using Visual Studio 2013 and Visual Basic.  A Visual Studio Express For Web project with VB source code is available to accompany this series which you can download.

The tutorial series comprises 11 sections in total. They cover the basics of web development using the ASP.NET MVC framework and the Entity Framework for data access. They are intended to be followed sequentially as each section builds on the knowledge imparted in the previous sections. The navigation path through the series is as follows:

  1. Getting Started
  2. Adding a Controller
  3. Adding a View
  4. Adding a Model
  5. Creating a Connection String and Working with SQL Server LocalDB
  6. Accessing Your Model's Data from a Controller
  7. Examining the Edit Methods and Edit View
  8. Adding Search
  9. Adding a New Field
  10. Adding Validation
  11. Examining the Details and Delete Methods

9. Adding a New Field

In this section you'll use Entity Framework Code First Migrations to migrate some changes to the model classes so the change is applied to the database.

By default, when you use Entity Framework Code First to automatically create a database, as you did earlier in this tutorial, Code First adds a table to the database to help track whether the schema of the database is in sync with the model classes it was generated from. If they aren't in sync, the Entity Framework throws an error. This makes it easier to track down issues at development time that you might otherwise only find (by obscure errors) at run time.

Setting up Code First Migrations for Model Changes

Navigate to Solution Explorer. Right click on the Movies.mdf file and select Delete to remove the movies database. If you don't see  the Movies.mdf file, click on the Show All Files icon shown below in the red outline.

Adding a new field

Build the application (Shift+Ctrl+B) to make sure there are no errors.

From the Tools menu, click Library Package Manager and then Package Manager Console.

Adding a new field

In the Package Manager Console window at the PM> prompt enter

Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext

Adding a new field

The Enable-Migrations command (shown above) creates a Configuration.vb file in a new Migrations folder.

Adding a new field

Visual Studio opens the Configuration.vb file. Replace the Seed method in the Configuration.vb file with the following code:

Protected Overrides Sub Seed(context As MovieDbContext)
    context.Movies.AddOrUpdate(Function(i) i.Title,
        New Movie() With { 
            .Title = "When Harry Met Sally", 
            .ReleaseDate = DateTime.Parse("1989-1-11"), 
            .Genre = "Romantic Comedy", 
            .Price = 7.99D 
        }, New Movie() With { 
            .Title = "Ghostbusters ", 
            .ReleaseDate = DateTime.Parse("1984-3-13"), 
            .Genre = "Comedy", 
            .Price = 8.99D 
        }, New Movie() With { 
            .Title = "Ghostbusters 2", 
            .ReleaseDate = DateTime.Parse("1986-2-23"), 
            .Genre = "Comedy", 
            .Price = 9.99D 
        }, New Movie() With { 
            .Title = "Rio Bravo", 
            .ReleaseDate = DateTime.Parse("1959-4-15"), 
            .Genre = "Western", 
            .Price = 3.99D 
        })
End Sub

Right click on the blue squiggly line under Movie and then hit Shift+Alt+F10. Click on Import 'MvcMovie.Models'.

Adding a new field

Doing so adds the following Imports statement to the top of the file:

Imports MvcMovie.Models

Code First Migrations calls the Seed method after every migration (that is, calling  update-database in the Package Manager Console), and this method updates rows that have already been inserted, or inserts them if they don't exist yet.

The AddOrUpdate method in the following code performs an "upsert" operation:

context.Movies.AddOrUpdate(Function(i) i.Title,
    New Movie() With { 
        .Title = "When Harry Met Sally", 
        .ReleaseDate = DateTime.Parse("1989-1-11"), 
        .Genre = "Romantic Comedy", 
        .Price = 7.99D 
    }

Because the Seed method runs with every migration, you can't just insert data, because the rows you are trying to add will already exist after the first migration that creates the database. The "upsert" operation prevents errors that would happen if you try to insert a row that already exists, but it overrides any changes to data that you may have made while testing the application. With test data in some tables you might not want that to happen: in some cases when you change data while testing you want your changes to remain after database updates. In that case you want to do a conditional insert operation: insert a row only if it doesn't already exist. 

The first parameter passed to the AddOrUpdate method specifies the property to use to check if a row already exists. For the test movie data that you are providing, the Title property can be used for this purpose since each title in the list is unique:

context.Movies.AddOrUpdate(Function(i) i.Title,

If you manually add a duplicate title, you'll get the following exception the next time you perform a migration. 

     Sequence contains more than one element

For more information about the AddOrUpdate method, see Take care with EF 4.3 AddOrUpdate Method.

Press CTRL-SHIFT-B to build the project. (The following steps will fail if you don't build at this point.)

The next step is to create a DbMigration class for the initial migration. This migration creates a new database. That's why you deleted the movie.mdf file in a previous step.

In the Package Manager Console window, enter the command add-migration Initial to create the initial migration.  The name "Initial" is arbitrary and is used to name the migration file created.

Adding a new field

Code First Migrations creates another class file in the Migrations folder (with the name {DateStamp}_Initial.vb ), and this class contains code that creates the database schema. The migration filename is pre-fixed with a timestamp to help with ordering.  Examine the {DateStamp}_Initial.vb  file; it contains the instructions to create the Movies table for the Movie DB. When you update the database in the instructions below, this {DateStamp}_Initial.vb  file will run and create the the DB schema. Then the Seed method will run to populate the DB with test data.

In the Package Manager Console, enter the command update-database to create the database and run the Seed method.

Adding a new field

If you get an error that indicates a table already exists and can't be created, it is probably because you ran the application after you deleted the database and before you executed update-database. In that case, delete the Movies.mdf file again and retry the update-database command. If you still get an error, delete the migrations folder and contents then start with the instructions at the top of this page (that is delete the Movies.mdf file then proceed to Enable-Migrations).

Run the application and navigate to the /Movies URL. The seed data is displayed.

Adding a new field

Adding a Rating Property to the Movie Model

Start by adding a new Rating property to the existing Movie class. Open the Models\Movie.vb file and add the Rating property like this one:

Public Property Rating As String

The complete Movie class now looks like the following code:

Public Class Movie
    Public Property ID As Integer
    Public Property Title As String
    <Display(Name:="Release Date")> 
    <DataType(DataType.Date)> 
    <DisplayFormat(DataFormatString:="{0:yyyy-MM-dd}", ApplyFormatInEditMode:=True)> 
    Public Property ReleaseDate As DateTime
    Public Property Genre As String
    Public Property Price As Decimal
    Public Property Rating As String
End Class

Build the application (Ctrl+Shift+B).

Because you've added a new field to  the Movie class, you also need to update the the binding white list so this new property will be included. Update the bind attribute for Create and Edit action methods to include the Rating property:

<Bind(Include:="ID,Title,ReleaseDate,Genre,Price,Rating")>

You also need to update the view templates in order to display, create and edit the new Rating property in the browser view.

Open the \Views\Movies\Index.vbhtml file and add a <th>Rating</th> column heading just after the Price column. Then add a <td> column near the end of the template to render the @item.Rating value. Below is what the updated Index.vbhtml view template looks like:

@ModelType IEnumerable(Of MvcMovie.Models.Movie)
@Code
ViewData("Title") = "Index"
End Code

<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") <br />
         <input type="submit" value="Filter" /></p>
    End Using
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(Function(model) model.Title)
        </th>
        <th>
            @Html.DisplayNameFor(Function(model) model.ReleaseDate)
        </th>
        <th>
            @Html.DisplayNameFor(Function(model) model.Genre)
        </th>
        <th>
            @Html.DisplayNameFor(Function(model) model.Price)
        </th>
        <th>
            @Html.DisplayNameFor(Function(model) model.Rating)
        </th>
        <th></th>
    </tr>

@For Each item In Model
    @<tr>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Title)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.ReleaseDate)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Genre)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Price)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) item.Rating)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", New With {.id = item.ID }) |
            @Html.ActionLink("Details", "Details", New With {.id = item.ID }) |
            @Html.ActionLink("Delete", "Delete", New With {.id = item.ID })
        </td>
    </tr>
Next

</table>

Next, open the \Views\Movies\Create.vbhtml file and add the Rating field with the following highlighed markup. This renders a text box so that you can specify a rating when a new movie is created.

        <div class="form-group">
            @Html.LabelFor(Function(model) model.Price, New With { .class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(Function(model) model.Price)
                @Html.ValidationMessageFor(Function(model) model.Price)
            </div>
        </div>

         <div class="form-group">
             @Html.LabelFor(Function(model) model.Rating, New With {.class = "control-label col-md-2"})
             <div class="col-md-10">
                 @Html.EditorFor(Function(model) model.Rating)
                 @Html.ValidationMessageFor(Function(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>

You've now updated the application code to support the new Rating property.

Run the application and navigate to the /Movies URL. When you do this, though, you'll see one of the following errors:

Adding a field

The model backing the 'MovieDBContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).

Adding a field

 

You're seeing this error because the updated Movie model class in the application is now different than the schema of the Movie table of the existing 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 based on the new model class schema. This approach is very convenient early in the development cycle when you are 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. For more information on Entity Framework database initializers, see Tom Dykstra's fantastic ASP.NET MVC/Entity Framework tutorial.
  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, we'll use Code First Migrations.

Update the Seed method so that it provides a value for the new column. Open Migrations\Configuration.vb file and add a Rating field to each Movie object.

 New Movie() With {
     .Title = "When Harry Met Sally",
     .ReleaseDate = DateTime.Parse("1989-1-11"),
     .Genre = "Romantic Comedy",
     .Price = 7.99D,
     .Rating = "PG"
}

Build the solution, and then open the Package Manager Console window and enter the following command:

add-migration Rating

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. The name Rating is arbitrary and is used to name the migration file. It's helpful to use a meaningful name for the migration step.

When this command finishes, Visual Studio opens the class file that defines the new DbMIgration derived class, and in the Up method you can see the code that creates the new column.

Imports System
Imports System.Data.Entity.Migrations
Imports Microsoft.VisualBasic

Namespace Migrations

    Public Partial Class Rating
        Inherits DbMigration
    
        Public Overrides Sub Up()
            AddColumn("dbo.Movies", "Rating", Function(c) c.String())
        End Sub
        
        Public Overrides Sub Down()
            DropColumn("dbo.Movies", "Rating")
        End Sub
    End Class
End Namespace

Build the solution, and then enter the update-database command in the Package Manager Console window.

Re-run the application and navigate to the /Movies URL. You can see the new Rating field.

Adding a new field

 

Click the Create New link to add a new movie. Note that you can add a rating.

Adding a new field

Click Create. The new movie, including the rating, now shows up in the movies listing:

Adding a new field

Now that the project is using migrations, you won't need to drop the database when you add a new field or otherwise update the schema. In the next section, we'll make more schema changes and use migrations to update the database.

You should also add the Rating field to the Edit, Details, and Delete view templates.

You could enter the "update-database" command in the Package Manager Console window again and no migration code would run, because the schema matches the model. However, running "update-database" will run the Seed method again, and if you changed any of the Seed data, the changes will be lost because the Seed method upserts data. You can read more about the Seed method in Tom Dykstra's popular ASP.NET MVC/Entity Framework tutorial.

In this section you saw how you can modify model objects and keep the database in sync with the changes. You also learned a way to populate a newly created database with sample data so you can try out scenarios. This was just a quick introduction to Code First. See Creating an Entity Framework Data Model for an ASP.NET MVC Application for a more complete tutorial on the subject. Next, let's look at how you can add richer validation logic to the model classes and enable some business rules to be enforced.