EF Core - Aplicando o Migrations


 Neste artigo veremos maneiras diferentes de aplicar o Migrations usando o EF Core.

Se você decidiu usar o Entity Framework (abordagem Code-First) em seu próximo projeto, mais cedo ou mais tarde, você terá que fazer alterações em seu banco de dados por meio de migrações.

Neste artigo, consideraremos como trabalhar com migrações do EF em geral e como executá-las como parte de seu pipeline de DevOps.

Vou usar como projeto exemplo uma Web API criada no ambiente do .NET 7.0.100-rc.2.22477.23.

A seguir vou mostrar de forma resumida as etapas para criar a solução e o projeto usando a ferramenta CLI e o PowerShell:

1- Crie uma pasta onde deseja criar a solução. Eu criei a pasta efcoremigrationsapp

2- Crie uma solução EFMigrations usando o comando :  dotnet new sln --name EFMigrations

3- Crie um projeto web ASP.NET Core Api vazio :  dotnet new web --name Api

4- A seguir inclua o projeto Api na solução EFMigrations:  dotnet sln add .\Api\Api.csproj

Abaixo temos a figura com a sequência de comandos usada:

5- Entre na pasta do projeto Api e inclua os seguintes pacotes no projeto:

dotnet add package Swashbuckle.AspNetCore  
dotnet add package Microsoft.EntityFrameworkCore 
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite 
Verificando o arquivo Api.csproj temos os pacotes instalados :
 <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.10">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

Assim vamos usar o provedor do SQLite de forma a ter um database leve e rápido.

A seguir vamos definir a configuração para fazer com que nossa aplicãção inicie usando o Swagger por padrão; Abra o arquivo launchSettings.json e adicione a seguinte linha aos perfis Api e IIS Express:

"launchUrl": "swagger"

A seguir inclua no arquivo Program.cs o código abaixo:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.Run();

Com isso concluímos a configuração padrão inicial básica. Vamos agora criar o arquivo de contexto e as entidades.

Aqui você pode abrir o projeto usando o VS Code ou o Visual Studio 2022 preview (pois estamos usando o .NET 7.0 que não foi lançado oficialmente ainda)

Criando a modelo e o contexto

Crie no projeto uma pasta Models e nesta pasta crie a classe Usuario :

public class Usuario
{
   public int Id { get; set; }
   public string Nome { get; set; } = string.Empty;
}

Crie outra pasta no projeto chamada Context e a seguir crie a classe AppDbContext que herda de DbContext :

using Api.Models;
using Microsoft.EntityFrameworkCore;
namespace Api.Context;
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions options) : base(options) { }
    public DbSet<Usuario> Usuarios => Set<Usuario>();
}

A seguir podemos registrar nosso contexto no container DI da ASP.NET Core; para fazer isso, precisamos modificar Program.cs da seguinte maneira:

using Api.Context;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
string connectionSqlite = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(connectionSqlite));
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapGet("/usuarios", (CancellationToken cancellationToken) =>
{
    using var scope = app.Services.CreateScope();
    var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    return dbContext.Usuarios.ToListAsync(cancellationToken);
});
app.Run();    

Configuramos o contexto para se conectar ao banco de dados SQLite e adicionamos um endpoint simples para obter todos os usuários do nosso banco de dados.

Como você pode ver, recuperamos a string de conexão do banco de dados da classe Configuration, então para fazer isso funcionar, também precisamos atualizar o appsettings.json da seguinte maneira :

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=SQLite.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Agora a configuração esta pronta e podemos iniciar aplicando o Migrations.

Instalando a ferramenta dotnet ef tools

As ferramentas de interface de linha de comando (CLI) para o Entity Framework Core executam tarefas de desenvolvimento em tempo de projeto.

Por exemplo, elas criam migrações, aplicam migrações e geram código para um modelo baseado em um banco de dados existente. Os comandos são uma extensão do comando dotnet , que faz parte do SDK do .NET Core. Essas ferramentas funcionam com projetos .NET Core.

Para verificar se a ferramenta esta instalada no seu ambiente emita o comando: dotnet ef

Você deverá visualiar a imagem acima com informações da versão.

Para instalar a ferramenta que aplica a migração podemos usar o comando :

dotnet tool install --global dotnet-ef

Para atualizar a ferramenta no seu ambiente o comando é :

dotnet tool update --global dotnet-ef

Aplicando a migração e criando o banco de dados

Para aplicar uma migração inicial e criar o arquivo de script e a pasta Migrations no projeto posicione-se na pasta do projeto e emita o comando :

dotnet ef migrations add Inicial

Ou, se tiver uma solução com mais de um projeto, posicione-se na pasta da solução e emita o comando:

dotnet ef migrations add Inicial --project NomeProjeto -s ProjetoAPi -c ArquivoContexto --verbose

Para o nosso exemplo o comando seria:

dotnet ef migrations add Inicial --project Api -s Api -c AppDbContext --verbose

Com isso será criada a pasta Migrations no projeto contendo o arquivo de script com este conteúdo :

public partial class Inicial : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Usuarios",
                columns: table => new
                {
                    Id = table.Column<int>(type: "INTEGER", nullable: false)
                        .Annotation("Sqlite:Autoincrement", true),
                    Nome = table.Column<string>(type: "TEXT", nullable: false)

                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Usuarios", x => x.Id);
                });
        }

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

Temos no arquivo de scripts , no método Up, os comandos que serão executados para criar a tabela Usuarios com as colunas Id e Nome , a chave primária e, no método Down, o comando para desfazer a operação.

Para aplicar estes comandos basta executar o seguinte comando:

dotnet ef database update

Ou, se tiver uma solução com mais de um projeto:

dotnet ef database update Inicial --project Api -s Api -c AppDbContext --verbose

Com isso teremos o banco de dados SQLite.db criado na raiz do projeto:

Se você quiser usar uma ferramenta para gerenciar e administrar o banco de dados SQLite pode usar o DB Browser for SQLite.

Aplicando migrações em CI/CD

Geralmente, há três maneiras de lidar com as migrações do EF no pipeline do DevOps:

1. Usar Scripts SQL

O Entity Framework Core nos permite gerar scripts SQL puros com base nas migrações. Tudo o que precisamos fazer é executar o seguinte comando:

dotnet ef migrations script

Para o nosso exemplo, posicionando-se na pasta Api e emitindo o comando acima iremos obter:

Com isso em mente, podemos criar scripts durante o pipeline Build, publicá-los como artefatos e executá-los durante o pipeline Release.

2. Gerando pacotes de migração do EF Core compatíveis com DevOps

Com o .NET 6, a equipe do EF Core lançou um novo recurso chamado mibrations bundles ou pacotes de migração. A ideia é que você possa gerar um arquivo executável contendo tudo o que é necessário para executar as migrações. Podemos criar um pacote executando o seguinte comando:

dotnet ef migrations bundle --configuration Bundle

Como resultado, obteremos o arquivo efbundle.exe que pode ser executado para aplicar as migrações ao banco de dados. Embora essa abordagem possa ser preferível em alguns casos, o algoritmo geral permanece o mesmo dos scripts SQL. Ainda precisamos produzir um arquivo como parte do processo de CI e executá-lo como parte do CD.

3. Aplicando o Mibrations na inicialização do aplicativo

O Entity Framework nos fornece uma opção para executar migrações vis código executando o método Database.Migrate().

Considerando isso, podemos simplesmente colocar esse método no início do Program.cs e executar as migrações durante a inicialização da aplicação. O código pode ter a seguinte aparência:

using Microsoft.EntityFrameworkCore;
using WebApi.DataAccess;
var builder = WebApplication.CreateBuilder(args);
string connectionSqlite = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionSqlite));

var app = builder.Build();
using var scope = app.Services.CreateScope();

await using var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

await dbContext.Database.MigrateAsync();
app.Run();

 

Embora essa seja a maneira mais rápida e fácil de lidar com migrações, a abordagem é arriscada e não é recomendada para aplicativos de produção.

Se vários nós de aplicativos forem executados simultaneamente, eles poderão tentar aplicar migrações e atualizar o banco de dados simultaneamente, o que causará falhas ou corrupção de dados.

E estamos conversados...

Pegue o projeto aqui :  EFCoreMigrationsApp.zip

"Bendito seja o Deus e Pai de nosso Senhor Jesus Cristo que, segundo a sua grande misericórdia, nos gerou de novo para uma viva esperança, pela ressurreição de Jesus Cristo dentre os mortos"
1 Pedro 1:3

Referências:


José Carlos Macoratti