EF Core - Populando dados iniciais (Data Seed) - II


Hoje vamos continuar abordando o recurso disponível a partir da versão 2.1 do EF Core que facilita a alimentação das tabelas criadas com dados iniciais.

Na primeira parte do artigo mostrei como usar o método HasData para alimentar uma tabela com dados iniciais.

Vejamos agora como alimentar dados em tabelas relacionadas no momento da sua criação.

Vamos continuar usando o projeto do artigo anterior e agora vamos criar duas novas entidades representadas pelas classes Autor e Livro onde vamos definir um relacionamento do tipo uma para muitos.

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

1- Classe Autor

    public class Autor
    {
        public int AutorId { get; set; }
        public string Nome { get; set; }
        public string Pais { get; set; }
        public List<Livro> Livros { get;  set; } = new List<Livro>();
    }

2- Classe Livro

    public class Livro
    {
        public int LivroId { get; set; }
        public string Titulo { get; set; }
        public string Lancamento { get; set; }
        public Autor Autor { get;  set;  }
    }

Os relacionamentos entre entidades em um modelo do Entity Framework são definidos pelas Propriedades de Navegação.  Uma propriedade de navegação é aquela que o provedor de banco de dados que está sendo usado não pode mapear para um tipo primitivo (ou escalar).

Acima definimos o relacionamento um-para-muitos entre Autor e Livros usando as propriedades de navegação e isso é expresso no VS 2019 nos seguintes diagrama de classes:

Além disso precisamos incluir os DbSets para Autor e Livro no arquivo de contexto AppDbContext:

public virtual DbSet<Autor> Autores { get; set; }
public virtual DbSet<Livro> Livros { get; set; }

Agora estamos prontos para definir o código no método OnModelCreating do arquivo de contexto.

Os dados relacionados devem ser adicionados separadamente por meio do método HasData aplicado ao EntityTypeBuilder da entidade relacionada.

Aqui está como você pode adicionar um autor junto com livros relacionados:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Autor>().HasData(
                new Autor
                {
                    AutorId = 1,
                    Nome = "Agatha Christie",
                    Pais = "England"
                }
            );
            modelBuilder.Entity<Livro>().HasData(
                new { LivroId = 1, Titulo = "O Misterioso Caso de Styles", Ano=1920, AutorId=1 },
                new { LivroId = 2, Titulo = "O Mistério do Trem Azul", Ano=1928, AutorId = 1 },
                new { LivroId = 3, Titulo = "A Terceira Moça", Ano=1966, AutorId = 1 },
                new { LivroId = 4, Titulo = "Nêmesis" , Ano= 1971, AutorId = 1 },
                new { LivroId = 5, Titulo = "Sócios no Crime" , Ano=1929, AutorId = 1 }
            );

        }

Se você observar atentamente o código usado vai perceber que primeiro atribuímos os dados para Autor e a seguir, com base no Autor incluido, obtemos o seu AutorId para poder incluir os livros relacionados.

Agora note que para atribuir os dados para a tabela Livros usamos HasData com um tipo Anônimo onde usamos a propriedade AutorId.

Mas de onde veio essa propriedade ?

Aqui é que entra o HasData, mesmo não tendo definido a propriedade AutorId, o HasData pode inferir este valor a partir do Modelo e do relacionamento definido.

Agora basta aplicar o migrations abrindo a janela do Package Manager Console e digitar o comando:
add-migration SeedDataAutorLivros

Será gerado o arquivo de scripts conforme abaixo:

using Microsoft.EntityFrameworkCore.Migrations;
namespace ConsoleEFCore1.Migrations
{
    public partial class SeedDataAutorLivros : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Autores",
                columns: table => new
                {
                    AutorId = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Nome = table.Column<string>(nullable: true),
                    Pais = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Autores", x => x.AutorId);
                });
            migrationBuilder.CreateTable(
                name: "Livros",
                columns: table => new
                {
                    LivroId = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Titulo = table.Column<string>(nullable: true),
                    Ano = table.Column<int>(nullable: false),
                    AutorId = table.Column<int>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Livros", x => x.LivroId);
                    table.ForeignKey(
                        name: "FK_Livros_Autores_AutorId",
                        column: x => x.AutorId,
                        principalTable: "Autores",
                        principalColumn: "AutorId",
                        onDelete: ReferentialAction.Restrict);
                });
            migrationBuilder.InsertData(
                table: "Autores",
                columns: new[] { "AutorId", "Nome", "Pais" },
                values: new object[] { 1, "Agatha Christie", "England" });
            migrationBuilder.InsertData(
                table: "Livros",
                columns: new[] { "LivroId", "Ano", "AutorId", "Titulo" },
                values: new object[,]
                {
                    { 1, 1920, 1, "O Misterioso Caso de Styles" },
                    { 2, 1928, 1, "O Mistério do Trem Azul" },
                    { 3, 1966, 1, "A Terceira Moça" },
                    { 4, 1971, 1, "Nêmesis" },
                    { 5, 1929, 1, "Sócios no Crime" }
                });
            migrationBuilder.CreateIndex(
                name: "IX_Livros_AutorId",
                table: "Livros",
                column: "AutorId");
        }
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Livros");
            migrationBuilder.DropTable(
                name: "Autores");
        }
    }
}

Temos os comandos no método Up() para criar as tabelas Autores e Livros e, em destaque temos o código para inserir os dados relacionados:         

           migrationBuilder.InsertData(
                table: "Autores",
                columns: new[] { "AutorId", "Nome", "Pais" },
                values: new object[] { 1, "Agatha Christie", "England" });
            migrationBuilder.InsertData(
                table: "Livros",
                columns: new[] { "LivroId", "Ano", "AutorId", "Titulo" },
                values: new object[,]
                {
                    { 1, 1920, 1, "O Misterioso Caso de Styles" },
                    { 2, 1928, 1, "O Mistério do Trem Azul" },
                    { 3, 1966, 1, "A Terceira Moça" },
                    { 4, 1971, 1, "Nêmesis" },
                    { 5, 1929, 1, "Sócios no Crime" }
                });

 

Agora podemos emitir o comando update-database na janela do Package Manage Console :

Apenas para testar, vamos incluir o código abaixo no método Main() para exibir os dados que acabamos de incluir nas tabelas Autores e Livros:

      static void Main(string[] args)
        {
            using (var _context = new AppDbContext())
            {
                var autores = _context.Autores
                                      .Include(l => l.Livros).ToList();
                foreach(var autor in autores)
                {
                    Console.WriteLine(autor.Nome);
                    foreach (var livro in autores[0].Livros)
                    {
                        Console.WriteLine($"{livro.Titulo} {livro.Ano}");
                    }
                }
            }
            Console.ReadLine();
        }

O resultado obtido será:

Na próxima parte do artigo veremos como incluir ou alterar dados durante migrações subsequentes.

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

"Se alguém ouvir as minhas palavras e não as guardar, eu não o julgo; porque eu não vim para julgar o mundo, e sim para salvá-lo.
Quem me rejeita e não recebe as minhas palavras tem quem o julgue; a própria palavra que tenho proferido, essa o julgará no último dia."
João 12:47,48

Referências:


José Carlos Macoratti