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
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.
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
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:
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
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.
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ário | Recomendação |
|---|---|
| CRUD simples, filtros diretos | LINQ-to-Entities (DbSet + Where/OrderBy) |
| Regra de negócio com múltiplas tabelas, precisa de transação atômica complexa | Stored Procedure (ExecuteSqlInterpolatedAsync/FromSqlInterpolated) |
| Relatório com agregações pesadas, performance crítica | Stored 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 |
É 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.