Parte 4, migrações do Razor Pages com EF Core no ASP.NET Core

Por Tom Dykstra, Jon P Smith e Rick Anderson

O aplicativo Web Contoso University demonstra como criar aplicativos Web do Razor Pages usando o EF Core e o Visual Studio. Para obter informações sobre a série de tutoriais, consulte o primeiro tutorial.

Se você encontrar problemas que não possa resolver, baixe o aplicativo concluído e compare esse código com o que você criou seguindo o tutorial.

Este tutorial apresenta o recurso de migrações do EF Core para gerenciar alterações do modelo de dados.

Quando um novo aplicativo é desenvolvido, o modelo de dados é alterado com frequência. Sempre que o modelo é alterado, ele fica fora de sincronia com o banco de dados. Esta série de tutoriais começa configurando o Entity Framework para criar o banco de dados, caso ele não exista. Sempre que o modelo de dados é alterado, o banco de dados precisa ser descartado. Na próxima vez em que o aplicativo for executado, a chamada para EnsureCreated recriará o banco de dados para que corresponda ao novo modelo de dados. A classe DbInitializer então é executada para propagar o novo banco de dados.

Essa abordagem para manter o BD em sincronia com o modelo de dados funciona bem até que o aplicativo tenha que ser colocado em produção. Quando o aplicativo é executado em produção, normalmente, ele armazena dados que precisam ser mantidos. O aplicativo não pode começar com um BD de teste sempre que uma alteração é feita (como a adição de uma nova coluna). O recurso Migrações do EF Core resolve esse problema, permitindo que o EF Core atualize o esquema de banco de dados em vez de criar um novo banco de dados.

Em vez de remover e recriar o banco de dados quando o modelo de dados é alterado, as migrações atualizam o esquema e retêm os dados existentes.

Observação

Limitações do SQLite

Este tutorial usa o recurso de migrações do Entity Framework Core sempre que possível. As migrações atualizam o esquema de banco de dados para corresponder às alterações no modelo de dados. No entanto, as migrações só fazem os tipos de alterações compatíveis com o mecanismo de banco de dados e os recursos de alteração de esquema do SQLite são limitados. Por exemplo, há suporte para adicionar uma coluna, mas não há suporte para a remoção de uma coluna. Se uma migração for criada para remover uma coluna, o comando ef migrations add terá êxito, mas o comando ef database update falhará.

A solução alternativa para as limitações do SQLite é escrever manualmente o código das migrações para executar uma recompilação de tabela quando algo na tabela for alterado. O código é inserido nos métodos Up e Down para uma migração e envolve:

  • Criar uma nova tabela.
  • Copiar dados da tabela antiga para a nova tabela.
  • Remover a tabela antiga.
  • Renomear a nova tabela.

Escrever código específico do banco de dados desse tipo está fora do escopo deste tutorial. Em vez disso, este tutorial descarta e recria o banco de dados sempre que uma tentativa de aplicar uma migração falha. Para saber mais, consulte os recursos a seguir:

Remover o banco de dados

Use o SSOX (Pesquisador de Objetos do SQL Server) para excluir o banco de dados ou execute o seguinte comando no PMC (Console do Gerenciador de Pacotes):

Drop-Database

Criar uma migração inicial

Executar os seguintes comandos no PMC:

Add-Migration InitialCreate
Update-Database

Remover EnsureCreated

Esta série de tutoriais começou usando o EnsureCreated. O EnsureCreated não cria uma tabela de histórico de migrações, portanto, não pode ser usada com migrações. Ele foi projetado para teste ou criação rápida de protótipos em que o banco de dados é removido e recriado com frequência.

Deste ponto em diante, os tutoriais usarão as migrações.

Em Program.cs, exclua a seguinte linha:

context.Database.EnsureCreated();

Execute o aplicativo e verifique se o banco de dados é propagado.

Métodos Para Cima e Para Baixo

O comando EF Coremigrations add gerou um código para criar o banco de dados. Esse código de migrações está no arquivo Migrations\<timestamp>_InitialCreate.cs. O método Up da classe InitialCreate cria as tabelas de banco de dados que correspondem aos conjuntos de entidades do modelo de dados. O método Down exclui-os, conforme mostrado no seguinte exemplo:

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

O código anterior refere-se à migração inicial. O código:

  • Foi gerado pelo comando migrations add InitialCreate.
  • É executado pelo comando database update.
  • Cria um banco de dados para o modelo de dados especificado pela classe de contexto do banco de dados.

O parâmetro de nome da migração (InitialCreate no exemplo) é usado para o nome do arquivo. O nome da migração pode ser qualquer nome de arquivo válido. É melhor escolher uma palavra ou frase que resume o que está sendo feito na migração. Por exemplo, uma migração que adicionou uma tabela de departamento pode ser chamada "AddDepartmentTable".

A tabela de histórico de migrações

  • Use SSOX ou a ferramenta SQLite para inspecionar o banco de dados.
  • Observe a adição de uma tabela __EFMigrationsHistory. A tabela __EFMigrationsHistory controla quais migrações foram aplicadas ao banco de dados.
  • Exibir os dados na tabela __EFMigrationsHistory. Mostra uma linha para a primeira migração.

O instantâneo do modelo de dados

As migrações criam um instantâneo do modelo de dados atual em Migrations/SchoolContextModelSnapshot.cs. Quando você adiciona uma migração, o EF determina o que foi alterado, comparando o modelo de dados atual com o arquivo de instantâneo.

Como o arquivo de instantâneo rastreia o estado do modelo de dados, não é possível excluir uma migração excluindo o arquivo <timestamp>_<migrationname>.cs. Para fazer backup da migração mais recente, use o comando migrations remove. migrations remove exclui a migração e garante que o instantâneo seja redefinido corretamente. Para saber mais, confira dotnet ef migrations remove.

Confira Redefinir todas as migrações para remover todas as migrações.

Aplicando migrações na produção

Recomendamos que os aplicativos de produção não chamem Database.Migrate na inicialização do aplicativo. Migrate não deve ser chamado de um aplicativo implantado em um farm de servidores. Se o aplicativo for escalado horizontalmente para várias instâncias de servidor, será difícil garantir que as atualizações do esquema de banco de dados não ocorram de vários servidores ou que estejam em conflito com o acesso de leitura/gravação.

A migração de banco de dados deve ser feita como parte da implantação e de maneira controlada. Abordagens de migração de banco de dados de produção incluem:

  • Uso de migrações para criar scripts SQL e uso dos scripts SQL na implantação.
  • Execução de dotnet ef database update em um ambiente controlado.

Solução de problemas

Se o aplicativo usar SQL Server LocalDB e exibir a seguinte exceção:

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

A solução pode ser executar dotnet ef database update em um prompt de comando.

Recursos adicionais

Próximas etapas

O próximo tutorial cria o modelo de dados adicionando propriedades da entidade e novas entidades.

Neste tutorial, o recurso de migrações do EF Core para o gerenciamento de alterações do modelo de dados é usado.

Caso tenha problemas que não consiga resolver, baixe o aplicativo concluído.

Quando um novo aplicativo é desenvolvido, o modelo de dados é alterado com frequência. Sempre que o modelo é alterado, ele fica fora de sincronia com o banco de dados. Este tutorial começa configurando o Entity Framework para criar o banco de dados, caso ele não exista. Sempre que o modelo de dados é alterado:

  • O BD é removido.
  • O EF cria um novo que corresponde ao modelo.
  • O aplicativo propaga o BD com os dados de teste.

Essa abordagem para manter o BD em sincronia com o modelo de dados funciona bem até que o aplicativo tenha que ser colocado em produção. Quando o aplicativo é executado em produção, normalmente, ele armazena dados que precisam ser mantidos. O aplicativo não pode começar com um BD de teste sempre que uma alteração é feita (como a adição de uma nova coluna). O recurso Migrações do EF Core resolve esse problema, permitindo que o EF Core atualize o esquema de banco de dados em vez de criar um novo banco de dados.

Em vez de remover e recriar o BD quando o modelo de dados é alterado, as migrações atualizam o esquema e retêm os dados existentes.

Remover o banco de dados

Use o SSOX (Pesquisador de Objetos do SQL Server) ou o comando database drop:

No PMC (Console do Gerenciador de Pacotes), execute o seguinte comando:

Drop-Database

Execute Get-Help about_EntityFrameworkCore no PMC para obter informações de ajuda.

Criar uma migração inicial e atualizar o BD

Crie o projeto e a primeira migração.

Add-Migration InitialCreate
Update-Database

Examinar os métodos Up e Down

O comando EF Coremigrations add gerou um código para criar o banco de dados. Esse código de migrações está no arquivo Migrations\<timestamp>_InitialCreate.cs. O método Up da classe InitialCreate cria as tabelas de banco de dados que correspondem aos conjuntos de entidades do modelo de dados. O método Down exclui-os, conforme mostrado no seguinte exemplo:

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

As migrações chamam o método Up para implementar as alterações do modelo de dados para uma migração. Quando um comando é inserido para reverter a atualização, as migrações chamam o método Down.

O código anterior refere-se à migração inicial. Esse código foi criado quando o comando migrations add InitialCreate foi executado. O parâmetro de nome da migração ("InitialCreate" no exemplo) é usado para o nome do arquivo. O nome da migração pode ser qualquer nome de arquivo válido. É melhor escolher uma palavra ou frase que resume o que está sendo feito na migração. Por exemplo, uma migração que adicionou uma tabela de departamento pode ser chamada "AddDepartmentTable".

Se a migração inicial foi criada e o BD existe:

  • O código de criação do BD é gerado.
  • O código de criação do BD não precisa ser executado porque o BD já corresponde ao modelo de dados. Se o código de criação do BD for executado, ele não fará nenhuma alteração porque o BD já corresponde ao modelo de dados.

Quando o aplicativo é implantado em um novo ambiente, o código de criação do BD precisa ser executado para criar o BD.

Anteriormente, o BD foi removido, e não existe mais. Então, as migrações criam o novo BD.

O instantâneo do modelo de dados

As migrações criam um instantâneo do esquema de banco de dados atual em Migrations/SchoolContextModelSnapshot.cs. Quando você adiciona uma migração, o EF determina o que foi alterado, comparando o modelo de dados com o arquivo de instantâneo.

Para excluir uma migração, use o seguinte comando:

Remove-Migration

O comando de exclusão de migrações exclui a migração e garante que o instantâneo seja redefinido corretamente.

Remover EnsureCreated e testar o aplicativo

Para o desenvolvimento inicial, EnsureCreated foi usado. Neste tutorial, as migrações são usadas. EnsureCreated tem as seguintes limitações:

  • Ignora as migrações e cria o BD e o esquema.
  • Não cria uma tabela de migrações.
  • Não pode ser usado com migrações.
  • Foi projetado para teste ou criação rápida de protótipos em que o BD é removido e recriado com frequência.

Remova EnsureCreated:

context.Database.EnsureCreated();

Execute o aplicativo e verifique se o BD é propagado.

Inspecionar o banco de dados

Use o Pesquisador de Objetos do SQL Server para inspecionar o BD. Observe a adição de uma tabela __EFMigrationsHistory. A tabela __EFMigrationsHistory controla quais migrações foram aplicadas ao BD. Exiba os dados na __EFMigrationsHistory tabela; ela mostra uma linha para a primeira migração. O último log no exemplo de saída da CLI anterior mostra a instrução INSERT que cria essa linha.

Execute o aplicativo e verifique se tudo funciona.

Aplicando migrações na produção

Recomendamos que os aplicativos de produção não chamem Database.Migrate na inicialização do aplicativo. Migrate não deve ser chamado em um aplicativo no farm de servidores. Por exemplo, se o aplicativo foi implantado na nuvem com escalabilidade horizontal (várias instâncias do aplicativo estão sendo executadas).

A migração de banco de dados deve ser feita como parte da implantação e de maneira controlada. Abordagens de migração de banco de dados de produção incluem:

  • Uso de migrações para criar scripts SQL e uso dos scripts SQL na implantação.
  • Execução de dotnet ef database update em um ambiente controlado.

O EF Core usa a tabela __MigrationsHistory para ver se uma migração precisa ser executada. Se o BD estiver atualizado, nenhuma migração será executada.

Solução de problemas

Baixar o aplicativo concluído.

O aplicativo gera a seguinte exceção:

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

Solução: execute dotnet ef database update

Recursos adicionais