Exercício: configurar uma migração

Concluído

Nesta unidade, você criará classes de entidade C# que serão mapeadas para tabelas no banco de dados local do SQLite. O recurso migrações do EF Core produz tabelas dessas entidades.

As migrações oferecem uma maneira de atualizar o esquema de banco de dados de maneira incremental.

Obter os arquivos de projeto

Para começar, obtenha os arquivos de projeto. Você tem algumas opções para obter os arquivos de projeto:

  • Usar o GitHub Codespaces
  • Clonar o repositório GitHub

Se você tem um runtime de contêiner compatível instalado, também pode usar a extensão Dev Containers para abrir o repositório em um contêiner com as ferramentas pré-instaladas.

Usar o GitHub Codespaces

Um codespace é um IDE (ambiente de desenvolvimento integrado) hospedado na nuvem. Se você estiver usando o GitHub Codespaces, vá para o repositório no navegador. Selecione Código e, em seguida, crie um codespace no branch main.

Clonar o repositório GitHub

Se você não estiver usando o GitHub Codespaces, poderá clonar o repositório GitHub do projeto e, em seguida, abrir os arquivos como uma pasta no Visual Studio Code.

  1. Abra um terminal de comando e clone o projeto do GitHub usando o prompt de comando:

    git clone https://github.com/MicrosoftDocs/mslearn-persist-data-ef-core
    
  2. Vá para a pasta mslearn-persist-data-ef-core e abra o projeto no Visual Studio Code:

    cd mslearn-persist-data-ef-core
    code .
    

Examine o código

Agora que você tem os arquivos de projeto com os quais trabalhar, vamos ver o que tem nele e examinar o código.

  • O projeto da API Web do ASP.NET Core está no diretório ContosoPizza. Os caminhos de arquivo que referimos neste módulo são relativos ao diretório ContosoPizza.
  • Services/PizzaService.cs é uma classe de serviço que define os métodos CRUD (create, read, update, and delete). Todos os métodos atualmente lançam System.NotImplementedException.
  • No Program.cs, o PizzaService é registrado com o sistema de injeção de dependência do ASP.NET Core.
  • Controllers/PizzaController.cs é um ApiController que explora um ponto de extremidade para os verbos POST, GET, PUT e DELETE. Esses verbos chamam os métodos CRUD correspondentes em PizzaService. PizzaService é injetado no construtor PizzaController.
  • A pasta Modelos contém os modelos que PizzaService e PizzaController usam.
  • Os modelos de entidade, Pizza.cs, Topping.cs e Sauce.cs, têm as seguintes relações:
    • Uma pizza pode ter uma ou mais coberturas.
    • Um cobertura pode ser usada em uma ou muitas pizzas.
    • Uma pizza pode ter apenas um molho, mas um molho pode ser usado em várias pizzas.

Criar o aplicativo

Para compilar o aplicativo no Visual Studio Code:

  1. No Explorador, clique com o botão direito do mouse no diretório ContosoPizza e selecione Abrir no Terminal Integrado.

    Um painel de terminal com escopo para o diretório ContosoPizza é aberto.

  2. Compile o aplicativo usando o seguinte comando:

    dotnet build
    

    O código deve ser construído compilado sem avisos ou erros.

Adicione pacotes NuGet e ferramentas EF Core

O mecanismo de banco de dados que é usado neste módulo é o SQLite. O SQLite é um mecanismo de banco de dados leve e baseado em arquivo. É uma boa opção para desenvolvimento e teste e também para implantações de produção em pequena escala.

Observação

Conforme mencionado anteriormente, os provedores de banco de dados no EF Core são conectáveis. O SQLite é uma boa escolha para este módulo porque é leve e multiplataforma. É possível usar o mesmo código para trabalhar com diferentes mecanismos de banco de dados, como o SQL Server e o PostgreSQL. É possível até mesmo usar diversos mecanismos de banco de dados no mesmo aplicativo.

Antes de começar, adicione os pacotes necessários:

  1. No painel do terminal, execute o seguinte comando:

    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    

    Este comando adiciona o pacote NuGet que contém o provedor de banco de dados SQLite EF Core e todas as respectivas dependências, incluindo os serviços do EF Core comum.

  2. Em seguida, execute este comando:

    dotnet add package Microsoft.EntityFrameworkCore.Design
    

    Este comando adiciona os pacotes necessários para as ferramentas do EF Core.

  3. Para concluir, execute este comando:

    dotnet tool install --global dotnet-ef
    

    Este comando instala o dotnet ef, a ferramenta que você usará para criar migrações e scaffolding.

    Dica

    Se dotnet ef já estiver instalado, você poderá atualizar com dotnet tool update --global dotnet-ef.

Modelos de transmissão e DbContext

Agora você adiciona e configura uma implementação DbContext. DbContext é um gateway por meio do qual você pode interagir com o banco de dados.

  1. Clique com o botão direito do mouse no diretório ContosoPizza e adicione uma pasta chamada Dados.

  2. No diretório Dados, crie um arquivo chamado PizzaContext.cs. Adicione o seguinte código ao arquivo vazio:

    using Microsoft.EntityFrameworkCore;
    using ContosoPizza.Models;
    
    namespace ContosoPizza.Data;
    
    public class PizzaContext : DbContext
    {
        public PizzaContext (DbContextOptions<PizzaContext> options)
            : base(options)
        {
        }
    
        public DbSet<Pizza> Pizzas => Set<Pizza>();
        public DbSet<Topping> Toppings => Set<Topping>();
        public DbSet<Sauce> Sauces => Set<Sauce>();
    }
    

    No código anterior:

    • O construtor aceita um parâmetro do tipo DbContextOptions<PizzaContext>. Esse construtor permite que o código externo seja transmitido na configuração, para que o mesmo DbContext possa ser compartilhado entre o código de teste e produção e até mesmo usado com diferentes provedores.
    • As propriedades DbSet<T> correspondem às tabelas a serem criadas no banco de dados.
    • Os nomes da tabela corresponderão aos nomes da propriedade DbSet<T> na classe PizzaContext. Você pode substituir esse comportamento, se necessário.
    • Quando uma instância é criada, PizzaContext expõe as propriedades Pizzas, Toppings e Sauces. As alterações feitas nas coleções expostas por essas propriedades serão propagadas para o banco de dados.
  3. No Program.cs, substitua // Add the PizzaContext com o seguinte código:

    builder.Services.AddSqlite<PizzaContext>("Data Source=ContosoPizza.db");
    

    O código anterior:

    • Registra PizzaContext com o sistema de injeção de dependência do ASP.NET Core.
    • Especifica que PizzaContext usará o provedor do banco de dados SQLite.
    • Define uma cadeia de conexão SQLite que aponta para um arquivo local, ContosoPizza.db.

    Observação

    Como o SQLite usa arquivos de banco de dados locais, não há problema em embutir a cadeia de conexão no código. No entanto, para bancos de dados de rede como PostgreSQL ou SQL Server, você sempre deve armazenar suas cadeias de conexão com segurança. Para desenvolvimento local, use o Gerenciador de Segredos. Para implantações de produção, considere um serviço como o Azure Key Vault.

  4. Além disso, no Program.cs, substitua // Additional using declarations pelo código a seguir.

    using ContosoPizza.Data;
    

    Esse código resolve as dependências na etapa anterior.

  5. Salve todas as alterações. O GitHub Codespaces salva as alterações automaticamente.

  6. Crie o aplicativo no terminal executando dotnet build. A compilação deve ser bem-sucedida sem avisos ou erros.

Criar e executar uma migração

Em seguida, crie uma migração que você possa usar para criar o banco de dados inicial.

  1. No terminal com escopo para a pasta de projeto ContosoPizza, execute o seguinte comando a fim de gerar uma migração para criar as tabelas de banco de dados:

    dotnet ef migrations add InitialCreate --context PizzaContext
    

    No comando anterior:

    • A migração é chamada de InitialCreate.
    • A opção --context especifica o nome da classe no projeto ContosoPizza, que deriva de DbContext.

    Um novo diretório Migrations é exibido na raiz do projeto ContosoPizza. O diretório contém um arquivo <timestamp>_InitialCreate.cs que descreve as alterações de banco de dados a serem convertidas em um script de alteração de DDL (linguagem de definição de dados).

  2. Execute o seguinte comando para aplicar a migração InitialCreate:

    dotnet ef database update --context PizzaContext
    

    Esse comando aplica a migração. ContosoPizza.db não existe, portanto, esse comando cria a migração no diretório do projeto.

    Dica

    Todas as plataformas dão suporte à ferramenta dotnet ef. No Visual Studio no Windows, você pode usar os cmdlets Add-Migration e Update-Database do PowerShell na janela integrada do Console do Gerenciador de Pacotes.

Inspecionar o banco de dados

O EF Core criou um banco de dados para seu aplicativo. Depois, vamos conferir o banco de dados usando a extensão do SQLite.

  1. No painel Explorador, clique com o botão direito do mouse no arquivo ContosoPizza.db e selecione Abrir Banco de Dados.

    Captura de tela mostrando a opção de menu Abrir Banco de Dados no painel Explorador do Visual Studio Code.

    Uma pasta SQLite Explorer aparece no painel Explorador.

    Captura de tela que mostra a pasta SQLite Explorer no painel Explorador.

  2. Selecione a pasta SQLite Explorer para expandir o nó e todos os respectivos nós filho. Clique com o botão direito do mouse em ContosoPizza.db e selecione Mostrar Tabela 'sqlite_master' para exibir o esquema de banco de dados completo e as restrições que a migração criou.

    Captura de tela que mostra a pasta SQLite Explorer expandida no painel Explorador.

    • Tabelas que correspondem a cada entidade foram criadas.
    • Os nomes de tabela foram retirados dos nomes das propriedades DbSet em PizzaContext.
    • Propriedades chamadas Id foram inferidas para serem campos de chave primária de incremento automático.
    • As convenções de nomenclatura de chave primária do EF Core e de restrição de chave estrangeira são PK_<primary key property> e FK_<dependent entity>_<principal entity>_<foreign key property>, respectivamente. Os espaços reservados <dependent entity> e <principal entity> correspondem aos nomes de classe da entidade.

    Observação

    Assim como ASP.NET Core MVC, o EF Core usa uma abordagem de convenção sobre a configuração. As convenções do EF Core reduzem o tempo de desenvolvimento ao inferir a intenção do desenvolvedor. Por exemplo, o núcleo EF infere uma propriedade nomeada Id ou <entity name>Id para ser a chave primária da tabela gerada. Se você optar por não adotar a convenção de nomenclatura, é necessário anotar a propriedade com o atributo [Key] ou configurá-la como uma chave no método OnModelCreating do DbContext.

Alterar o modelo e atualizar o esquema de banco de dados

Seu gerente da Contoso Pizza fornece alguns novos requisitos que forçam você a alterar seus modelos de entidade. Nas etapas a seguir, você modificará os modelos usando atributos de mapeamento, também chamados de anotações de dados.

  1. Em Models\Pizza.cs, faça as seguintes alterações:

    1. Adicione uma diretiva using para System.ComponentModel.DataAnnotations.
    2. Adicione um atributo [Required] antes da propriedade Name para marcar a propriedade conforme necessário.
    3. Adicione um atributo [MaxLength(100)] antes da propriedade Name para especificar um comprimento máximo de cadeia de caracteres de 100.

    O arquivo Pizza.cs atualizado deve ser semelhante ao seguinte:

    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Pizza
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public Sauce? Sauce { get; set; }
    
        public ICollection<Topping>? Toppings { get; set; }
    }
    
  2. Em Models\Sauce.cs, faça as seguintes alterações:

    1. Adicione uma diretiva using para System.ComponentModel.DataAnnotations.
    2. Adicione um atributo [Required] antes da propriedade Name para marcar a propriedade conforme necessário.
    3. Adicione um atributo [MaxLength(100)] antes da propriedade Name para especificar um comprimento máximo de cadeia de caracteres de 100.
    4. Adicione uma propriedade bool chamada IsVegan.

    O arquivo Sauce.cs atualizado deve ser semelhante ao seguinte:

    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Sauce
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public bool IsVegan { get; set; }
    }
    
  3. Em Models\Topping.cs, faça as seguintes alterações:

    1. Adicione using diretivas para System.ComponentModel.DataAnnotations e System.Text.Json.Serialization.

    2. Adicione um atributo [Required] antes da propriedade Name para marcar a propriedade conforme necessário.

    3. Adicione um atributo [MaxLength(100)] antes da propriedade Name para especificar um comprimento máximo de cadeia de caracteres de 100.

    4. Adicione uma propriedade decimal chamada Calories imediatamente após a propriedade Name.

    5. Adicione uma propriedade Pizzas do tipo ICollection<Pizza>?. Essa alteração torna Pizza-Topping uma relação muitos para muitos.

    6. Adicione um atributo [JsonIgnore] à propriedade Pizzas.

      Importante

      Esse atributo serve para impedir que entidades Topping incluam a propriedade Pizzas quando o código da API Web serializa a resposta para JSON. Sem essa alteração, uma coleção serializada de coberturas incluiria uma coleção de cada pizza que usa a cobertura. Cada pizza nessa coleção conteria uma coleção de coberturas, que cada uma conteria novamente uma coleção de pizzas. Esse tipo de loop infinito é chamado de referência circular e não pode ser serializado.

    O arquivo Topping.cs atualizado deve ser semelhante ao seguinte:

    using System.ComponentModel.DataAnnotations;
    using System.Text.Json.Serialization;
    
    namespace ContosoPizza.Models;
    
    public class Topping
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public decimal Calories { get; set; }
    
        [JsonIgnore]
        public ICollection<Pizza>? Pizzas { get; set; }
    }
    
  4. Salve todas as alterações e execute dotnet build.

  5. Execute o seguinte comando para gerar uma migração para criar as tabelas de banco de dados:

    dotnet ef migrations add ModelRevisions --context PizzaContext
    

    Esse comando cria uma migração chamada ModelRevisions.

    Observação

    Você verá esta mensagem: Foi feito scaffold de uma operação, o que pode resultar na perda de dados. Examine a precisão da migração. Essa mensagem foi exibida porque você alterou a relação de Pizza para Topping de "um para muitos" para "muitos para muitos", o que exige que uma coluna de chave estrangeira existente seja removida. Como você ainda não tem dados em seu banco de dados, essa alteração não é problemática. No entanto, em geral, é uma boa ideia verificar a migração gerada quando esse aviso é exibido para garantir que a migração não cause o truncamento ou exclusão de nenhum dado.

  6. Execute o seguinte comando para aplicar a migração ModelRevisions:

    dotnet ef database update --context PizzaContext
    
  7. Na barra de título do painel SQLite Explorer, selecione o botão Atualizar Bancos de Dados.

    Captura de tela que mostra o botão Atualizar Bancos de Dados na barra de título do SQLite Explorer.

  8. Na pasta SQLite Explorer, clique com o botão direito do mouse em ContosoPizza.db. Selecione Mostrar Tabela 'sqlite_master' para exibir o esquema e as restrições completas do banco de dados.

    Importante

    A extensão do SQLite reutilizará as guias abertas do SQLite.

    • Uma tabela de junção PizzaTopping foi criada para representar a relação muitos para muitos entre pizzas e coberturas.
    • Novos campos foram adicionados a Toppings e a Sauces.
      • Calories é definido como uma coluna text porque o SQLite não tem um tipo decimal correspondente.
      • Da mesma forma, IsVegan é definido como uma coluna integer. O SQLite não define um tipo bool.
      • Em ambos os casos, o EF Core gerencia a tradução.
    • A coluna Name em cada tabela foi marcada como not null, mas o SQLite não tem uma restrição MaxLength.

    Dica

    Os provedores de banco de dados EF Core mapeiam um esquema de modelo para os recursos de um banco de dados específico. Embora o SQLite não implemente uma restrição correspondente para MaxLength, outros bancos de dados implementam, como o SQL Server e o PostgreSQL.

  9. Na pasta SQLite Explorer, clique com o botão direito do mouse na tabela _EFMigrationsHistory e selecione Mostrar Tabela. A tabela contém uma lista de todas as migrações aplicadas ao banco de dados. Como você executou duas migrações, há duas entradas: uma para a migração InitialCreate e outra para ModelRevisions.

Observação

Este exercício usou atributos de mapeamento (anotações de dados) para mapear modelos para o banco de dados. Como alternativa ao mapeamento de atributos, é possível usar a API fluente ModelBuilder para configurar modelos. Ambas as abordagens são válidas, mas alguns desenvolvedores preferem uma à outra.

Você usou migrações para definir e atualizar um esquema de banco de dados. Na próxima unidade, você concluirá os métodos no PizzaService que manipulam dados.

Verificar seu conhecimento

1.

Em uma classe de entidade, o que é a convenção de nomenclatura de propriedade para uma chave primária?