EF Core - Apresentando e usando o Migrations - I


Hoje vamos iniciar uma série de artigos que apresenta o Migrations e seus principais recursos.

Atualmente (junho/2020) a versão estável do EF Core é a versão 3.1.4, mas já temos as versões preview do EF core 5.0.0 conforme você pode conferir aqui.

Para acompanhar as mudanças feitas no modelo de dados e manter a sincronia do modelo com o banco de dados o EF Core oferece o recurso Migrations ou Migrações que permite atualizar de forma incremental o esquema do banco de dados e assim mantê-lo sincronizado com o modelo de dados do seu projeto preservando os dados existentes.

A seguir temos as principais tarefas do Migrations usando ferramentas de linha de comando e APIs:

Crie uma migração. Gere código que pode atualizar o banco de dados para sincronizá-lo com um conjunto de alterações de modelo.

Para poder usar o recurso Migrations a primeira coisa a fazer é instalar as ferramentas do Entity Framework Core que ajudam nas tarefas de desenvolvimento em tempo de projeto e são usadas para gerar migrações e fazer a engenharia reversa do esquema do banco de dados. Podemos usar essas ferramentas de duas maneiras:

O EF Core utiliza um conjunto de convenções para criar um modelo com base nas suas entidades, dessa forma para aplicar o recurso Migrations no seu projeto você precisa definir :

  1. Um modelo de entidades que são classes com propriedades;
  2. Uma classe de contexto que herda de DbContext e definir os DbSets para as entidades a mapear;
  3. Definir a string de conexão com o banco de dados no arquivo appsettings.json
  4. Registrar o contexto como um serviço usando AddDbContext
  5. Definir o provedor do banco de dados e a string de conexão usada;

Nota:  Você pode usar o método OnModelCreating na classe de contexto para configurar o seu modelo de entidades e também pode aplicar atributos às suas entidades usando Data Annotations

Criando e aplicando o Migrations

Vamos agora criar e aplicar o Migrations usando como exemplo um projeto ASP .NET Core do tipo API que já esta criado e pronto para ser usado.

  • Modelo de entidade : Aluno
  • Arquivo de contexto :  AppDbContext
  • String de conexão em appsettings.json
  • Configuração do serviço, provedor e string de conexão no método ConfigureServices do arquivo Startup

Nota: Veja como criar o projeto de exemplo usado neste artigo aqui.

O processo de criar e aplicar o Migrations envolve duas etapas:

  1. Criar a migração - Cria o arquivo de script SQL contendo os comandos da migração;
  2. Aplicar a migração - Execute o arquivo de script gerado e aplica os comandos ao banco de dados;

Como já dissemos, nosso esquema de banco de dados deve estar alinhado com o modelo e todas as alterações em um modelo de  precisam ser migradas para o próprio banco de dados.

Durante o desenvolvimento geralmente é necessários alterar o modelo de entidades fazendo alterações nas propriedades do modelo ou incluindo e removendo propriedades DbSet<> da classe de contexto, etc.

No nosso exemplo temos um projeto configurado e pronto para ser usado e vamos iniciar criando uma migração e a seguir aplicando a migração para que sejam gerados o banco de dados AlunosDBWeb e a tabela Alunos no SQL Server.

a - Criando a migração

Para criar a migração no VS 2019 Community podemos abrir a janela do Package Manager Console (PMC) e digitar o comando:   

Add-Migration  NomeDaMigração [options]

Este comando precisa do pacote Microsoft.EntityFrameworkCore.Tools instalado para funcionar.

Usando o VS Code e a ferramenta de linha de comando CLI o comando é:

dotnet ef migrations add NomeDaMigração [options]

Para este comando funcionar temos instalar a ferramenta de linha de comando do EF Core globalmente usando o comando:

dotnet tool install --global dotnet-ef

Vamos abrir o projeto ASP .NET Core e na janela do PMC digitar o comando: add-migration MigracaoInicial

O projeto será compilado e o arquivo de script gerado conforme abaixo:

using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace AspNet_EFCoreApi.Migrations
{
    public partial class MigracaoInicial : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Alunos",
                columns: table => new
                {
                    Id = table.Column<Guid>(nullable: false),
                    Nome = table.Column<string>(nullable: true),
                    Idade = table.Column<int>(nullable: true),
                    Ativo = table.Column<bool>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Alunos", x => x.Id);
                });
        }
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Alunos");
        }
    }
}

Temos aqui a classe MigracaoInicial que herda  de Migration e define dois métodos : Up e Down.

No método Up() temos os comandos para criar a tabela Alunos onde as colunas da tabela possuem os  mesmos nomes das propriedades definidos na classe Aluno. No método Down temos o comando para remover a tabela criada caso desejamos remover a migração.

Esses dois métodos são usados na classe que herda de Migration para definir as alterações que serão aplicadas ao banco de dados, sendo que no método Up() aplica o script e o método Down() reverte a aplicação feita.

Assim, se você cometeu algum erro e deseja remover a migração basta digitar: Remove-Migration o método Down será executado e a migração será revertida.

Note que é criada uma pasta Migrations no projeto contendo os arquivos de migração:

Aqui temos os arquivos:

O registro de data e hora no nome do arquivo ajuda a mantê-los em ordem cronológica, para que você possa ver a progressão das alterações.

b - Aplicando a migração

Depois de criar nossa migração com sucesso, precisamos aplicá-la para que as alterações entrem em vigor no banco de dados.

Existem várias maneiras de aplicar migrações (usando scripts SQL, usando o método Database.Migrate ou usando métodos de linha de comando) e, como fizemos na criação, vamos usar a abordagem de métodos de linha de comando.

Usando o VS 2019 na janela do PMC podemos emitir o comando: update-database [options]

Usando o VS Code e a ferramenta NET CLI o comando usado é : dotnet ef database update [options]

Para o nosso exemplo vamos digitar o comando : update-database

Ao final do processamento teremos o banco de dados AlunosDBWeb e a tabela Alunos criada no SQL Server conforme as convenções usadas pelo EF Core e segundo as configurações definidas em nosso projeto ASP .NET Core.

Abrindo o SQL Server Management Studio podemos confirmar a criação do banco de dados AlunosDBWeb e a tabela Alunos contendo as colunas : Id, Nome, Idade e Ativo.

Observe a tabela _EFMigrationsHistory que foi criada no banco de dados.

O EF Core usa esta tabela para rastrear todas as migrações aplicadas. Portanto, isso significa que, se criarmos outra migração em nosso código e aplicá-lo, o EF Core aplicará apenas a migração recém-criada.

Mas como o EF Core sabe qual migração precisa ser aplicada ?

Bem, ele armazena um ID exclusivo no _EFMigrationsHistory, que é um nome exclusivo do arquivo de migração criado com a migração e nunca executa arquivos com o mesmo nome novamente.

Podemos confirmar isso abrindo essa tabela e comparar o valor de MigrationId com o nome do arquivo de migração gerado.

Cada migração é aplicada em uma transação SQL, o que significa que toda a migração é bem-sucedida ou falha. Se tivermos várias migrações para aplicar, elas serão aplicadas na ordem exata em que são criadas.

Agora observe que a coluna Nome foi gerada como um nvarchar(max) a partir da propriedade Nome do tipo string na entidade Aluno.

    using System;

    public class Aluno
    {
        public Guid Id { get; set; }
        public string Nome { get; set; }
        public int? Idade { get; set; }
        public bool Ativo { get; set; }
   }

Podemos corrigir isso definindo na propriedade Nome da enitdade Aluno dois atributos atributo que serão aplicados via Data Annotations que usa o namespace System.ComponentModel.DataAnnotations :

using System;
using System.ComponentModel.DataAnnotations;
namespace AspNet_EFCoreApi.Models
{
    public class Aluno
    {
        public Guid Id { get; set; }

        [Required(ErrorMessage ="Informe o nome do aluno")]
        [MaxLength(80, ErrorMessage = "O nome deve ter no máximo 80 caracteres")]
        public string Nome { get; set; }

        public int? Idade { get; set; }
        public bool Ativo { get; set; }
    }
}

Nota: Poderíamos ter feito o mesmo ajuste no método OnModelCreating conforme mostrado a seguir:

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
            modelBuilder.Entity<Aluno>()
                .ToTable("Alunos");
            modelBuilder.Entity<Aluno>()
                .Property(s => s.Id)
                .HasColumnName("AlunoId");
            modelBuilder.Entity<Aluno>()
                .Property(s => s.Nome)
                .IsRequired()
                .HasMaxLength(80);
            modelBuilder.Entity<Aluno>()
                .Property(s => s.Idade)
                .IsRequired(false);
  }

Assim agora podemos aplicar outra migração emitindo o comando: add-migration AjusteColunaNome

A seguir vamos aplicar a migração digitando: update-database

E se verificarmos o banco de dados veremos a nova migração emitida e agora a coluna nome possui o tamanho de 80 conforme esperado:

Na próxima parte do artigo veremos como adicionar um código customizado ao arquivo de migração.

Pegue o projeto aqui :   AlunosWeb.zip (sem as referências)

"Porque pela graça sois salvos, por meio da fé; e isto não vem de vós, é dom de Deus.
Não vem das obras, para que ninguém se glorie;"
Efésios 2:8,9

Referências:


José Carlos Macoratti