Adición de un nuevo campo

por Rick Anderson

Nota:

Existe una versión actualizada de este tutorial, disponible aquí, donde se usa la versión más reciente de Visual Studio. El nuevo tutorial usa ASP.NET Core MVC, que proporciona muchas mejoras en este tutorial.

En este tutorial se muestra ASP.NET Core MVC con controladores y vistas. Razor Pages es una nueva alternativa en ASP.NET Core, un modelo de programación basado en páginas que facilita la compilación de interfaces de usuario web y hace que sean más productivas. Se recomienda probar el tutorial de las páginas de Razor antes que la versión MVC. El tutorial de las páginas de Razor:

  • Es más fácil de seguir.
  • Abarca más características.
  • Es el enfoque preferido para el desarrollo de nuevas aplicaciones.

En esta sección usará Migraciones de Code First de Entity Framework para migrar algunos cambios a las clases de modelo para que el cambio se aplique a la base de datos.

De forma predeterminada, cuando se usa Code First de Entity Framework para crear automáticamente una base de datos, como hizo anteriormente en este tutorial, Code First agrega una tabla a la base de datos para ayudar a realizar un seguimiento de si el esquema de la base de datos está sincronizado con las clases de modelo de las que se generó. Si no están sincronizados, Entity Framework produce un error. Esto facilita el seguimiento de problemas durante el desarrollo que, de lo contrario, solo encontraría (por errores ocultos) en tiempo de ejecución.

Configuración de migraciones de Code First para cambios de modelo

Vaya al Explorador de soluciones. Haga clic con el botón derecho en el archivo Movies.mdf y seleccione Eliminar para quitar la base de datos de películas. Si no ve el archivo Movies.mdf, haga clic en el icono Mostrar todos los archivos que se muestra a continuación en el contorno rojo.

Screenshot that shows the Movies Controller dot c s tab and Solution Explorer open. The Show All Files icon is circled in red.

Compile la aplicación para asegurarse de que no hay ningún error.

En el menú Tools (Herramientas), haga clic en Administrador de paquetes NuGet y luego en Consola del Administrador de paquetes.

Add Pack Man

En la ventana Consola del administrador de paquetes en el símbolo del sistema PM>, escriba

Enable-Migrations -ContextTypeName MvcMovie.Models.MovieDBContext

Screenshot that shows the Package Manager Console window. Text in the Enable Migrations command is highlighted.

El comando Enable-Migrations (mostrado anteriormente) crea un archivo Configuration.cs en una nueva carpeta de Migraciones.

Screenshot that shows the Solution Explorer. The Configuration dot c s subfolder of the Migrations folder is selected.

Visual Studio abre el archivo Configuration.cs. Reemplace el método Seed en el archivo Configuration.cs por el código siguiente:

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
       }
   );
   
}

Mantenga el puntero sobre la línea ondulada roja debajo de Movie y haga clic en Show Potential Fixes y, a continuación, haga clic en usarMvcMovie.Models;

Screenshot that shows the Show Potential Fixes menu. Using M V C Movie dot Models is selected and a cannot be found alert is shown.

Al hacerlo, se agregan las siguientes instrucciones using:

using MvcMovie.Models;

Nota:

Migraciones de Code First llama al método Seed después de cada migración (es decir, llama a update-database en la consola del Administrador de paquetes) y este método actualiza las filas que ya se han insertado o las inserta si aún no existen.

El método AddOrUpdate del código siguiente realiza una operación "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
    }

Como el método Seed se ejecuta con cada migración, no puede simplemente insertar los datos, porque las filas que intenta agregar ya estarán ahí después de la primera migración que crea la base de datos. La operación "upsert" evita los errores que se producirían si intentara insertar una fila que ya existe, pero invalida cualquier cambio en los datos que haya podido realizar mientras probaba la aplicación. Es posible que no quiera que esto ocurra con los datos de prueba de algunas tablas: en algunos casos, cuando cambia los datos durante las pruebas, querrá que sus cambios permanezcan después de las actualizaciones de la base de datos. En ese caso, querrá realizar una operación de inserción condicional: insertar una fila solo si aún no existe.

El primer parámetro que se pasa al método AddOrUpdate especifica la propiedad que se usará para comprobar si ya existe una fila. Para los datos de la película de prueba que está proporcionando, se puede usar la propiedad Title para este fin, ya que cada apellido de la lista es único:

context.Movies.AddOrUpdate(i => i.Title,

Este código supone que los títulos son únicos. Si agrega manualmente un título duplicado, obtendrá la siguiente excepción la próxima vez que realice una migración.

La secuencia contiene más de un elemento

Para más información sobre el método AddOrUpdate, consulte Cuídese con el método AddOrUpdate de EF 4.3.

Presione CTRL-MAYÚS-B para compilar el proyecto (se producirá un error en los pasos siguientes si no se compila en este momento).

El siguiente paso es crear una clase DbMigration para la migración inicial. Esta migración crea una nueva base de datos, es por ello que eliminó el archivo movie.mdf en un paso anterior.

En la ventana Consola del administrador de paquetes, escriba el comando add-migration Initial para crear la migración inicial. El nombre "Initial" es arbitrario y se usa para asignar un nombre al archivo de migración.

Screenshot that shows the Package Manager Console. Text in the add migration command is highlighted.

Migraciones de Code First crea otro archivo de clase en la carpeta Migraciones (con el nombre {DateStamp}_Initial.cs) y esta clase contiene el código que crea el esquema de la base de datos. El nombre de archivo de la migración lleva una marca de tiempo para ayudar con el orden. Examine el archivo {DateStamp}_Initial.cs, contiene las instrucciones para crear la tabla Movies para Movie DB. Al actualizar la base de datos en las instrucciones siguientes, este archivo {DateStamp}_Initial.cs se ejecutará y creará el esquema de la base de datos. A continuación, el método Seed se ejecutará para rellenar la base de datos con datos de prueba.

En la consola del Administrador de paquetes, escriba el comando update-database para crear la base de datos y ejecutar el método Seed.

Screenshot that shows the Package Manager Console. The update database command is in the window.

Si recibe un error que indica que ya existe una tabla y no se puede crear, es probable que se deba a que ejecutó la aplicación después de eliminar la base de datos y antes de ejecutar update-database. En ese caso, vuelva a eliminar el archivo Movies.mdf y vuelva a intentarlo con el comando update-database. Si sigue recibiendo un error, elimine la carpeta y el contenido de las migraciones, comience con las instrucciones de la parte superior de esta página (es decir, elimine el archivo Movies.mdf y, a continuación, continúe con Enable-Migrations). Si sigue recibiendo un error, abra el Explorador de objetos de SQL Server y quite la base de datos de la lista. Si recibe un error que indica "No se puede adjuntar el archivo .mdf como base de datos", quite la propiedad Initial Catalog como parte de la cadena de conexión en el archivo web.config.

Ejecute la aplicación y navegue a la URL /Movies. Se muestran los datos propagados.

Screenshot that shows the M V C Movie Index with four movies listed.

Adición de una propiedad de clasificación al modelo Movie

Empiece agregando una nueva propiedad Rating a la clase existente Movie. Abra el archivo Models\Movie.cs y agregue una propiedad Rating como esta:

public string Rating { get; set; }

La clase Movie completa ahora tiene el siguiente aspecto:

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; }
}

Compile la aplicación (Ctrl+Mayús+B).

Dado que ha agregado un nuevo campo a la clase Movie, también debe actualizar la lista de enlaces permitidos para que se incluya esta nueva propiedad. Actualice el atributo bind para que los métodos de acción Create y Edit incluyan la propiedad Rating:

[Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")]

También debe actualizar las plantillas de vista para mostrar, crear y editar la nueva propiedad Rating en la vista del explorador.

Abra el archivo \Views\Movies\Index.cshtml y agregue un encabezado de columna <th>Rating</th> justo después de la columna Precio. A continuación, agregue una columna <td> cerca del final de la plantilla para representar el valor @item.Rating. A continuación se muestra el aspecto de la plantilla de vista Index.cshtml actualizada:

@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>

A continuación, abra el archivo \Views\Movies\Create.cshtml y agregue el campo Rating con el marcado resaltado siguiente. Esto representa un cuadro de texto para que pueda especificar una clasificación cuando se crea una nueva película.

<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")
}

Ahora ha actualizado el código de la aplicación para que admita la nueva propiedad Rating.

Ejecute la aplicación y navegue a la URL /Movies. Sin embargo, al hacerlo, verá uno de los siguientes errores:

Screenshot that shows an Exception User Unhandled Error.

El modelo que respalda al contexto "MovieDBContext" ha cambiado desde que se creó la base de datos. Considere la posibilidad de usar Migraciones de Code First para actualizar la base de datos (https://go.microsoft.com/fwlink/?LinkId=238269).

Screenshot that shows a browser with the notification Server Error in Application.

Este error se muestra porque la clase del modelo Movie actualizada en la aplicación es diferente del esquema de la tabla Movie de la base de datos existente. (No hay ninguna columna Rating en la tabla de la base de datos).

Este error se puede resolver de varias maneras:

  1. Haga que Entity Framework quite de forma automática la base de datos y la vuelva a crear basándose en el nuevo esquema de la clase del modelo. Este enfoque resulta muy conveniente al principio del ciclo de desarrollo cuando se está realizando el desarrollo activo en una base de datos de prueba; permite desarrollar rápidamente el esquema del modelo y la base de datos juntos. La desventaja es que se pierden los datos existentes en la base de datos, así que ¡no use este enfoque en una base de datos de producción! Usar un inicializador para inicializar automáticamente una base de datos con datos de prueba suele ser una manera productiva de desarrollar una aplicación. Para obtener más información sobre los inicializadores de la base de datos de Entity Framework, consulte el tutorial de ASP.NET MVC/Entity Framework.
  2. Modifique explícitamente el esquema de la base de datos existente para que coincida con las clases del modelo. La ventaja de este enfoque es que se conservan los datos. Puede realizar este cambio de forma manual o mediante la creación de un script de cambio de base de datos.
  3. Use Migraciones de Code First para actualizar el esquema de la base de datos.

Para este tutorial se usa Migraciones de Code First.

Actualice el método Seed para que proporcione un valor para la nueva columna. Abra el archivo Migrations\Configuration.cs y agregue un campo Rating a cada objeto Movie.

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

Compile la solución y, a continuación, abra la ventana de la consola del Administrador de paquetes y escriba el siguiente comando:

add-migration Rating

El comando add-migration indica el marco de trabajo de migración para examinar el modelo actual con el esquema de base de datos actual y crear el código con el que se migrará la base de datos al nuevo modelo. El nombre Clasificación es arbitrario y se usa para asignar un nombre al archivo de migración. Resulta útil emplear un nombre descriptivo para el archivo de migración.

Cuando este comando finaliza, Visual Studio abre el archivo de clase que define la nueva clase DbMigration derivada y, en el método Up, puede ver el código que crea la nueva columna.

public partial class AddRatingMig : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Movies", "Rating", c => c.String());
    }
    
    public override void Down()
    {
        DropColumn("dbo.Movies", "Rating");
    }
}

Compile la solución y escriba el comando update-database en la ventana consola del Administrador de paquetes.

En la imagen siguiente se muestra la salida en la ventana Consola del Administrador de paquetes (la Clasificación pendiente de marca de fecha será diferente).

Screenshot that shows the Package Manager Console window with the update database command entered.

Vuelva a ejecutar la aplicación y vaya a la dirección URL de /Movies. Puede ver el nuevo campo Clasificación.

Screenshot that shows the M V C Movie Index list with the Rating field added.

Haga clic en el vínculo Crear nueva para agregar una nueva película. Tenga en cuenta que puede agregar una clasificación.

7_CreateRioII

Haga clic en Crear. La nueva película, incluida la clasificación, ahora aparece en la lista de películas:

7_ourNewMovie_SM

Ahora que el proyecto usa migraciones, no tendrá que quitar la base de datos al agregar un nuevo campo o actualizar el esquema. En la sección siguiente, realizaremos más cambios de esquema y usaremos migraciones para actualizar la base de datos.

También debe agregar el campo Rating a las plantillas Editar, Detalles y Eliminar vista.

Puede volver a escribir el comando "update-database" en la ventana de la Consola del Administrador de paquetes y no se ejecutará ningún código de migración, ya que el esquema coincide con el modelo. Sin embargo, la ejecución de "update-database" volverá a ejecutar el método Seed y, si ha cambiado alguno de los datos de propagación, los cambios se perderán porque el método Seed actualiza/inserta (upsert) los datos. Puede obtener más información sobre el método Seed en el popular tutorial de ASP.NET MVC/Entity Framework de Tom Dykstra.

En esta sección ha visto cómo puede modificar objetos de modelo y mantener la base de datos sincronizada con los cambios. También ha aprendido a rellenar una base de datos recién creada con datos de ejemplo para probar escenarios. Se trata de una introducción rápida a Code First, consulte Creación de un modelo de datos de Entity Framework para una aplicación MVC de ASP.NET para obtener un tutorial más completo sobre el tema. A continuación, veamos cómo puede agregar una lógica de validación más completa a las clases de modelo y permitir que se apliquen algunas reglas de negocio.