EF Core - Populando dados iniciais (Data Seed)


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

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.

Dentre os muitos recursos que foram incorporados no EF Core, e, disponível a partir da versão 2.1, temos o preenchimento inicial dos dados de uma maneira bem mais simples e fácil.

O preenchimento de suas tabelas com dados iniciais, o famoso Seed Data, é feito no momento que o banco e as tabelas são criadas. Fazemos a alimentação das tabelas com valores iniciais para fins de demonstração, prova de conceitos, etc.

A partir da versão 2.1, o Entity Framework Core possui uma API formal para aplicar dados iniciais ao banco de dados como parte de sua migração - o método HasData do método EntityTypeBuilder<T>,  exposto pelo método ModelBuilder.Entity<T>, acessível no método OnModelCreating da classe DbContext.

No projeto Console .NET Core com EF Core de exemplo inclua na pasta Models a classe Teste:

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

    public class Teste
    {
        public int TesteId { get; set; }
        public string Nome { get; set; }
        public string Email { get; set; }
    }

A seguir inclua no arquivo AppDbContext o DbSet<T> para esta entidade:

    public class AppDbContext : DbContext
    {
        public AppDbContext()
        {
        }
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {
        }
        public virtual DbSet<Customer> Customers { get; set; }
        public virtual DbSet<Order> Orders { get; set; }
        public virtual DbSet<Product> Products { get; set; }
        public virtual DbSet<Category> Categories { get; set; }

      
 public virtual DbSet<Teste> Testes { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=Macoratti;Initial Catalog=Northwind;Integrated Security=True");
        }
    }

O objetivo é definir o mapeamento entre a entidade Teste e a tabela Testes que iremos criar usando o Migrations.

Vamos definir o código no método OnModelCreating para preencher a tabela Testes com dados iniciais usando o método HasData() :

    public class AppDbContext : DbContext
    {
        public AppDbContext()
        {
        }
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {
        }
        public virtual DbSet<Customer> Customers { get; set; }
        public virtual DbSet<Order> Orders { get; set; }
        public virtual DbSet<Product> Products { get; set; }
        public virtual DbSet<Category> Categories { get; set; }
        public virtual DbSet<Teste> Testes { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=Macoratti;Initial Catalog=Northwind;Integrated Security=True");
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Teste>().HasData(
                new Teste
                {
                    TesteId = 1,
                    Nome = "Macoratti",
                    Email = "macoratti@yahoo.com"
                },
                new Teste
                {
                    TesteId = 2,
                    Nome = "Miriam",
                    Email = "mimi@hotmail.com"
                }
            );
        }
    }

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

Atenção !!!

Como o banco de dados Northwind e as tabelas já existem, com exceção da tabela Testes, e, como esta é a primeira migração, devemos abrir o arquivo de script gerado e remover todo o código, deixando apenas o código abaixo:

   public partial class SeedDataTestes : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Testes",
                columns: table => new
                {
                    TesteId = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Nome = table.Column<string>(nullable: true),
                    Email = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Testes", x => x.TesteId);
                });
            migrationBuilder.InsertData(
                table: "Testes",
                columns: new[] { "TesteId", "Email", "Nome" },
                values: new object[] { 1, "macoratti@yahoo.com", "Macoratti" });
            migrationBuilder.InsertData(
                table: "Testes",
                columns: new[] { "TesteId", "Email", "Nome" },
                values: new object[] { 2, "mimi@hotmail.com", "Miriam" });
        }
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "Testes");
        }
    }

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 na tabela Testes:

    class Program
    {
        static void Main(string[] args)
        {
            using (var _context = new AppDbContext())
            {
                var livros = _context.Testes.ToList();
                foreach(var t in testes)
                {
                    Console.WriteLine($"{t.TesteId} {t.Nome} {t.Email}" );
                }
            }
            Console.ReadLine();
        }
    }

O resultado obtido será:

Na próxima parte do artigo veremos como alimentar tabelas com relacionamentos.

Recomenda-se não usar funções para gerar chaves para qualquer campo do modelo.
Por exemplo Guid.NewGuid() para um campo Guid ou DateTime.Now para um campo DateTime. Isso resultará em uma nova migração, pois o valor muda toda vez que você executa o comando add-migration. Portanto, sempre use valores codificados.

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

Disse Jesus: "Se, pois, o Filho vos libertar, verdadeiramente sereis livres."
João 8:36

Referências:


José Carlos Macoratti