Módulo 1 · C# — Capítulo 13

ADO.NET e EF Core

Aprofundando o Entity Framework Core: DbContext, DbSet, migrations e change tracking — e como isso convive com as procedures que você escreveu no Módulo 2.

1. Recapitulando o que o Módulo 2 já mostrou

No capítulo 13 do Módulo 2, você viu as três formas de chamar uma procedure a partir do C# (ADO.NET, Dapper, EF Core). Este capítulo aprofunda especificamente o Entity Framework Core como ORM completo — não só como forma de chamar procedures, mas como mapeia classes inteiras para tabelas e rastreia mudanças automaticamente.

2. DbContext e DbSet — o coração do EF Core

C# AppDbContext.cs
public class AppDbContext : DbContext
{
    public DbSet<Cliente> Clientes => Set<Cliente>();
    public DbSet<Pedido> Pedidos   => Set<Pedido>();

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Cliente>(entidade =>
        {
            entidade.ToTable("Cliente");
            entidade.HasKey(c => c.Id);
            entidade.Property(c => c.Nome).HasMaxLength(100).IsRequired();
            entidade.Property(c => c.Email).HasMaxLength(150).IsRequired();
        });
    }
}

DbContext é o equivalente conceitual de uma conexão + unidade de trabalho; cada DbSet<T> representa uma tabela, navegável com LINQ (capítulo 9) — o LINQ escrito contra um DbSet é traduzido em SQL de verdade na hora da execução.

C# LINQ traduzido para SQL
var clientesAtivos = await contexto.Clientes
    .Where(c => c.Ativo)
    .OrderBy(c => c.Nome)
    .ToListAsync();

// o EF Core traduz isso, nos bastidores, em algo como:
// SELECT Id, Nome, Email, Ativo FROM Cliente WHERE Ativo = 1 ORDER BY Nome
Atenção

Nem toda expressão C# tem tradução para SQL. Métodos muito específicos de .NET (formatação customizada, chamadas a bibliotecas externas) dentro de um .Where() podem lançar exceção em tempo de execução por não terem equivalente SQL. Mantenha a lógica dentro do LINQ-to-Entities simples; regras mais complexas ficam melhor numa procedure (Módulo 2) ou aplicadas depois de .ToListAsync(), já em memória.

3. Change Tracking — a mágica por trás do SaveChanges

Diferente do Dapper (onde você mesmo escreve o UPDATE), o EF Core rastreia mudanças em entidades carregadas por ele e gera o SQL de escrita automaticamente:

C# change tracking em ação
var cliente = await contexto.Clientes.FirstAsync(c => c.Id == 42);

cliente.Email = "novo@email.com"; // o EF Core percebe essa mudança

await contexto.SaveChangesAsync(); // gera e executa o UPDATE necessário, sozinho
FirstAsync() Change Tracker guarda o "estado original" e compara com o atual SaveChangesAsync()

Fig. 1 — O EF Core compara o estado atual da entidade com o estado carregado do banco para decidir o que mudou.

4. Migrations — versionando o esquema do banco

Migrations são a forma do EF Core de versionar mudanças de esquema (criar tabela, adicionar coluna) como código C#, aplicáveis de forma incremental — parecido em espírito com versionar os arquivos .sql de procedures, como você viu no capítulo 12 do Módulo 2.

bash terminal
dotnet ef migrations add CriarTabelaCliente
dotnet ef database update

5. Quando usar LINQ-to-Entities e quando usar as procedures do Módulo 2

CenárioRecomendação
CRUD simples, filtros diretosLINQ-to-Entities (DbSet + Where/OrderBy)
Regra de negócio com múltiplas tabelas, precisa de transação atômica complexaStored Procedure (ExecuteSqlInterpolatedAsync/FromSqlInterpolated)
Relatório com agregações pesadas, performance críticaStored Procedure — mais controle sobre o plano de execução
Operação em lote (muitos registros de uma vez)Table-Valued Parameter via procedure (capítulo 8, Módulo 2) — EF Core não é otimizado para lotes grandes por padrão
Dica

É perfeitamente normal (e comum) um mesmo projeto usar EF Core para a maior parte do CRUD e procedures para as operações mais pesadas ou críticas — como o usp_PedidoCheckout do projeto prático do Módulo 2. Um não substitui o outro; eles resolvem partes diferentes do mesmo problema.

6. Onde isso te leva

Com EF Core aprofundado, o último capítulo deste módulo fecha com boas práticas e Clean Code — como organizar tudo que você aprendeu (OOP, LINQ, async, padrões de projeto, EF Core) de um jeito que continua legível conforme o projeto cresce.

📌 Resumo do capítulo

  • DbContext representa a unidade de trabalho; cada DbSet<T> é uma tabela navegável via LINQ.
  • LINQ escrito contra um DbSet é traduzido em SQL real — nem toda expressão C# tem tradução possível.
  • Change tracking permite alterar uma propriedade e deixar o SaveChangesAsync() gerar o UPDATE sozinho.
  • Migrations versionam o esquema do banco como código C#.
  • EF Core (LINQ) e procedures não são excludentes — use cada um onde é mais forte.

✏️ Praticando

  1. Configure um AppDbContext com DbSet<Cliente> e DbSet<Pedido>, mapeando para as tabelas do Módulo 2.
  2. Crie e aplique uma migration para uma nova coluna Telefone em Cliente.
  3. Escreva um método que carrega um cliente, altera o e-mail e chama SaveChangesAsync() — depois confirme no SSMS que o UPDATE foi aplicado.