Compartir vía


Parte 4. Razor Pages con migraciones de EF Core en ASP.NET Core

Por Tom Dykstra, Jon P Smith y Rick Anderson

En la aplicación web Contoso University se muestra cómo crear aplicaciones web Razor Pages con EF Core y Visual Studio. Para obtener información sobre la serie de tutoriales, consulte el primer tutorial.

Si surgen problemas que no puede resolver, descargue la aplicación completada y compare ese código con el que ha creado siguiendo el tutorial.

En este tutorial se presenta la característica de migraciones de EF Core para administrar cambios en el modelo de datos.

Cuando se desarrolla una aplicación nueva, el modelo de datos cambia con frecuencia. Cada vez que el modelo cambia, este deja de estar sincronizado con la base de datos. Esta serie de tutoriales se ha iniciado con la configuración de Entity Framework para crear la base de datos si no existe. Cada vez que cambia el modelo de datos, es necesario quitar la base de datos. La próxima vez que se ejecute la aplicación, la llamada a EnsureCreated vuelve a crear la base de datos para que coincida con el nuevo modelo de datos. Después, se ejecuta la clase DbInitializer para inicializar la nueva base de datos.

Este enfoque para mantener la base de datos sincronizada con el modelo de datos funciona bien hasta que sea necesario implementar la aplicación en producción. Cuando se ejecuta la aplicación en producción, normalmente está almacenando datos que hay que mantener. No se puede iniciar la aplicación con una prueba de base de datos cada vez que se hace un cambio (por ejemplo, agregar una nueva columna). La característica Migraciones de EF Core soluciona este problema habilitando EF Core para actualizar el esquema de la base de datos en lugar de crear una.

En lugar de quitar y volver a crear la base de datos cuando cambian los datos del modelo, las migraciones actualizan el esquema y conservan los datos existentes.

Nota

Limitaciones de SQLite

En este tutorial se usa la característica de migraciones de Entity Framework Core siempre que sea posible. Las migraciones actualizan el esquema de la base de datos para que coincida con los cambios en el modelo de datos. Pero las migraciones solo permiten los tipos de cambios que admite el motor de base de datos, y las funciones de cambio de esquema de SQLite son limitadas. Por ejemplo, se permite agregar una columna, pero no eliminarla. Si se crea una migración para quitar una columna, el comando ef migrations add se ejecuta correctamente, pero el comando ef database update produce un error.

La solución alternativa para las limitaciones de SQLite consiste en escribir de forma manual el código de migraciones para llevar a cabo una recompilación de la tabla cuando cambie algún elemento de esta. El código va en los métodos Up y Down para una migración e implica lo siguiente:

  • Crear una tabla.
  • Copiar datos de la tabla antigua a la nueva tabla.
  • Eliminar la tabla anterior.
  • Cambiar el nombre de la tabla nueva.

La escritura de código específico de la base de datos de este tipo está fuera del ámbito de este tutorial. En su lugar, en este tutorial se quita y se vuelve a crear la base de datos cada vez que se produce un error al intentar aplicar una migración. Para obtener más información, vea los siguientes recursos:

Eliminación de la base de datos

Use Explorador de objetos de SQL Server (SSOX) para eliminar la base de datos, o bien ejecute el comando siguiente en la Consola del administrador de paquetes (PMC):

Drop-Database

Crear una migración inicial

Ejecute los comandos siguientes en la Consola del administrador de paquetes:

Add-Migration InitialCreate
Update-Database

Quitar EnsureCreated

Esta serie de tutoriales se ha iniciado con EnsureCreated. EnsureCreated no crea una tabla de historial de migraciones y, por tanto, no se puede usar con las migraciones. Está diseñado para crear prototipos rápidos o de prueba donde la base de datos se quita y se vuelve a crear con frecuencia.

A partir de este punto, en los tutoriales se usarán las migraciones.

En Program.cs, elimine la línea siguiente:

context.Database.EnsureCreated();

Ejecute la aplicación y compruebe que la base de datos se ha inicializado.

Métodos Up y Down

El comando migrations add de EF Core ha generado código para crear la base de datos. Este código de migraciones se encuentra en el archivo Migrations\<timestamp>_InitialCreate.cs. El método Up de la clase InitialCreate crea las tablas de base de datos que se corresponden a los conjuntos de entidades del modelo de datos. El método Down las elimina, tal como se muestra en el ejemplo siguiente:

using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;

namespace ContosoUniversity.Migrations
{
    public partial class InitialCreate : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Course",
                columns: table => new
                {
                    CourseID = table.Column<int>(nullable: false),
                    Title = table.Column<string>(nullable: true),
                    Credits = table.Column<int>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Course", x => x.CourseID);
                });

            migrationBuilder.CreateTable(
                name: "Student",
                columns: table => new
                {
                    ID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    LastName = table.Column<string>(nullable: true),
                    FirstMidName = table.Column<string>(nullable: true),
                    EnrollmentDate = table.Column<DateTime>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Student", x => x.ID);
                });

            migrationBuilder.CreateTable(
                name: "Enrollment",
                columns: table => new
                {
                    EnrollmentID = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    CourseID = table.Column<int>(nullable: false),
                    StudentID = table.Column<int>(nullable: false),
                    Grade = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
                    table.ForeignKey(
                        name: "FK_Enrollment_Course_CourseID",
                        column: x => x.CourseID,
                        principalTable: "Course",
                        principalColumn: "CourseID",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_Enrollment_Student_StudentID",
                        column: x => x.StudentID,
                        principalTable: "Student",
                        principalColumn: "ID",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "IX_Enrollment_CourseID",
                table: "Enrollment",
                column: "CourseID");

            migrationBuilder.CreateIndex(
                name: "IX_Enrollment_StudentID",
                table: "Enrollment",
                column: "StudentID");
        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Enrollment");

            migrationBuilder.DropTable(
                name: "Course");

            migrationBuilder.DropTable(
                name: "Student");
        }
    }
}

El código anterior es para la migración inicial. El código:

  • Lo ha generado el comando migrations add InitialCreate.
  • Lo ejecuta el comando database update.
  • Crea una base de datos para el modelo de datos especificado por la clase de contexto de base de datos.

El parámetro de nombre de la migración (InitialCreate en el ejemplo) se usa para el nombre de archivo. El nombre de la migración puede ser cualquier nombre de archivo válido. Es más recomendable elegir una palabra o frase que resuma lo que se hace en la migración. Por ejemplo, una migración que ha agregado una tabla de departamento podría denominarse "AddDepartmentTable".

La tabla de historial de migraciones

  • Use SSOX o la herramienta SQLite para inspeccionar la base de datos.
  • Observe la adición de una tabla __EFMigrationsHistory. En la tabla __EFMigrationsHistory se realiza el seguimiento de las migraciones que se han aplicado a la base de datos.
  • Vea los datos de la tabla __EFMigrationsHistory. Muestra una fila para la primera migración.

La instantánea del modelo de datos

Las migraciones crean una instantánea del modelo de datos actual en Migrations/SchoolContextModelSnapshot.cs. Cuando se agrega una migración, EF determina qué ha cambiado mediante la comparación del modelo de datos actual con el archivo de instantánea.

Como el archivo de instantánea realiza el seguimiento del estado del modelo de datos, no se puede eliminar una migración mediante la eliminación del archivo <timestamp>_<migrationname>.cs. Para quitar la migración más reciente, use el comando migrations remove. migrations remove elimina la migración y garantiza que la instantánea se restablece correctamente. Para obtener más información, vea dotnet ef migrations remove.

Consulte el artículo sobre cómo restablecer todas las migraciones para quitar todas las migraciones

Aplicar las migraciones en producción

Se recomienda que las aplicaciones de producción no llamen a Database.Migrate al iniciar la aplicación. Migrate no se debe llamar desde una aplicación que se implementa en una granja de servidores. Si la aplicación se escala horizontalmente en varias instancias de servidor, es difícil garantizar que las actualizaciones del esquema de la base de datos no se realicen en varios servidores o que estén en conflicto con el acceso de lectura y escritura.

La migración de bases de datos debe realizarse como parte de la implementación y de un modo controlado. Entre los métodos de migración de base de datos de producción se incluyen:

  • Uso de las migraciones para crear scripts SQL y uso de scripts SQL en la implementación.
  • Ejecución de dotnet ef database update desde un entorno controlado.

Solución de problemas

Si en la aplicación se usa SQL Server LocalDB y se muestra la excepción siguiente:

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

La solución puede ser ejecutar dotnet ef database update en un símbolo del sistema.

Recursos adicionales

Pasos siguientes

En el tutorial siguiente se crea el modelo de datos y se agregan propiedades de entidad y entidades nuevas.

En este tutorial, se usa la característica de migraciones de EF Core para administrar cambios en el modelo de datos.

Si experimenta problemas que no puede resolver, descargue la aplicación completada.

Cuando se desarrolla una aplicación nueva, el modelo de datos cambia con frecuencia. Cada vez que el modelo cambia, este deja de estar sincronizado con la base de datos. Este tutorial se inició con la configuración de Entity Framework para crear la base de datos si no existía. Cada vez que los datos del modelo cambian:

  • Se quita la base de datos.
  • EF crea una que coincide con el modelo.
  • La aplicación inicializa la base de datos con datos de prueba.

Este enfoque para mantener la base de datos sincronizada con el modelo de datos funciona bien hasta que sea necesario implementar la aplicación en producción. Cuando se ejecuta la aplicación en producción, normalmente está almacenando datos que hay que mantener. No se puede iniciar la aplicación con una prueba de base de datos cada vez que se hace un cambio (por ejemplo, agregar una nueva columna). La característica Migraciones de EF Core soluciona este problema habilitando EF Core para actualizar el esquema de la base de datos en lugar de crear una.

En lugar de quitar y volver a crear la base de datos cuando los datos del modelo cambian, las migraciones actualizan el esquema y conservan los datos existentes.

Eliminación de la base de datos

Use el Explorador de objetos de SQL Server (SSOX) o el comando database drop:

En la Consola del Administrador de paquetes (PMC), ejecute el comando siguiente:

Drop-Database

Ejecute Get-Help about_EntityFrameworkCore desde PMC para obtener información de ayuda.

Creación de una migración inicial y actualización de la base de datos

Compile el proyecto y cree la primera migración.

Add-Migration InitialCreate
Update-Database

Examinar los métodos Up y Down

El comando migrations add de EF Core ha generado código para crear la base de datos. Este código de migraciones se encuentra en el archivo Migrations\<timestamp>_InitialCreate.cs. El método Up de la clase InitialCreate crea las tablas de base de datos que se corresponden a los conjuntos de entidades del modelo de datos. El método Down las elimina, tal como se muestra en el ejemplo siguiente:

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Course",
            columns: table => new
            {
                CourseID = table.Column<int>(nullable: false),
                Title = table.Column<string>(nullable: true),
                Credits = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Course", x => x.CourseID);
            });

        migrationBuilder.CreateTable(
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Enrollment");

        migrationBuilder.DropTable(
            name: "Course");

        migrationBuilder.DropTable(
            name: "Student");
    }
}

Las migraciones llaman al método Up para implementar los cambios del modelo de datos para una migración. Cuando se escribe un comando para revertir la actualización, las migraciones llaman al método Down.

El código anterior es para la migración inicial. Ese código se creó cuando se ejecutó el comando migrations add InitialCreate. El parámetro de nombre de la migración ("InitialCreate" en el ejemplo) se usa para el nombre de archivo. El nombre de la migración puede ser cualquier nombre de archivo válido. Es más recomendable elegir una palabra o frase que resuma lo que se hace en la migración. Por ejemplo, una migración que ha agregado una tabla de departamento podría denominarse "AddDepartmentTable".

Si la migración inicial está creada y la base de datos existe:

  • Se genera el código de creación de la base de datos.
  • El código de creación de la base de datos no tiene que ejecutarse porque la base de datos ya coincide con el modelo de datos. Si el código de creación de la base de datos se está ejecutando, no hace ningún cambio porque la base de datos ya coincide con el modelo de datos.

Cuando la aplicación se implementa en un entorno nuevo, se debe ejecutar el código de creación de la base de datos para crear la base de datos.

Anteriormente, la base de datos se eliminó, de modo que ya no existe y ahora se crea mediante las migraciones.

La instantánea del modelo de datos

Las migraciones crean una instantánea del esquema de la base de datos actual en Migrations/SchoolContextModelSnapshot.cs. Cuando se agrega una migración, EF determina qué ha cambiado mediante la comparación del modelo de datos con el archivo de instantánea.

Para eliminar una migración, use el comando siguiente:

Remove-Migration

El comando remove migrations elimina la migración y garantiza que la instantánea se restablece correctamente.

Eliminación de EnsureCreated y prueba de la aplicación

Para el desarrollo inicial se ha utilizado EnsureCreated. En este tutorial, se usan las migraciones. EnsureCreated tiene las siguientes limitaciones:

  • Omite las migraciones y crea la base de datos y el esquema.
  • No crea una tabla de migraciones.
  • No puede usarse con las migraciones.
  • Está diseñado para crear prototipos rápidos o de prueba donde se quita y vuelve a crear la base de datos con frecuencia.

Quite EnsureCreated:

context.Database.EnsureCreated();

Ejecute la aplicación y compruebe que la base de datos se haya inicializado.

Inspección de la base de datos

Use el Explorador de objetos de SQL Server para inspeccionar la base de datos. Observe la adición de una tabla __EFMigrationsHistory. La tabla __EFMigrationsHistory realiza un seguimiento de las migraciones que se han aplicado a la base de datos. Examine los datos de la tabla __EFMigrationsHistory, muestra una fila para la primera migración. En el último registro del ejemplo de salida de la CLI anterior se muestra la instrucción INSERT que crea esta fila.

Ejecute la aplicación y compruebe que todo funciona correctamente.

Aplicar las migraciones en producción

Se recomienda que las aplicaciones de producción no llamen a Database.Migrate al iniciar la aplicación. No debe llamarse a Migrate desde una aplicación en la granja de servidores. Por ejemplo, si la aplicación se ha implementado en la nube con escalado horizontal (se ejecutan varias instancias de la aplicación).

La migración de bases de datos debe realizarse como parte de la implementación y de un modo controlado. Entre los métodos de migración de base de datos de producción se incluyen:

  • Uso de las migraciones para crear scripts SQL y uso de scripts SQL en la implementación.
  • Ejecución de dotnet ef database update desde un entorno controlado.

EF Core usa la tabla __MigrationsHistory para ver si es necesario ejecutar las migraciones. Si la base de datos está actualizada, no se ejecuta ninguna migración.

Solución de problemas

Descargue la aplicación completada.

La aplicación genera la siguiente excepción:

SqlException: Cannot open database "ContosoUniversity" requested by the login.
The login failed.
Login failed for user 'user name'.

Solución: Ejecute dotnet ef database update.

Recursos adicionales