Entity Framework em APIs modernas
Boas práticas conceituais em do uso de Entity Framework em aplicações ASP.NET Core
O Entity Framework Core é sem dúvidas o ORM mais famoso e utilizado no ecossistema .NET para construção de APIs modernas. Acredite ou não que apesar de sua popularidade, grande parte dos problemas atribuídos a ele não está na ferramenta em si, mas no uso inadequado de seus conceitos fundamentais acredita?
Em APIs ASP.NET Core, o EF Core não deve ser visto apenas como um atalho ou simples ferramenta de acesso para o banco de dados, mas como uma abstração que impõe um modelo mental específico. Quando esse modelo e padrão é respeitado, os ganhos em manutenção, produtividade e performance são significativos. Quando ele é ignorado, aí sim começam a ser gerados os gargalos de performance, acoplamento excessivo e uso exagerado de memória(Quem nunca recebeu um "Out of memory"?).
Este artigo vai te mostrar os principais pontos do EF Core e sua utilização em APIs modernas e pra finalizar tem um projeto real simples, alinhado às boas práticas apresentadas.
O papel do Entity Framework em APIs ASP.NET Core
Em uma API moderna, o EF Core normalmente ocupa a camada de persistência, tendo como suas principais responsabilidades:
- Mapear as entidades de domínio transformando as em tabelas
- Traduzir LINQ em SQL
- Controlar o estado das entidades
- Gerenciar as transações de forma direta
Ele resolve um problema muito recorrente em sistemas corporativos: padronizar o acesso a dados sem espalhar SQL pela aplicação. Em contrapartida, adiciona uma camada extra que exige decisões conscientes.
O EF Core funciona melhor quando:
- O domínio é relevante
- As regras mudam com frequência
- A manutenção é contínua
- O time prioriza clareza e previsibilidade
Versões do .NET e compatibilidade com Entity Framework Core
O Entity Framework Core evoluiu junto com o .NET. Cada versão do EF Core é pensada para trabalhar de forma ideal com a sua versão compativel de runtime.
Um exemplo de compatibilidade entre as versões
.NET ----> EF Core
.NET 6 ----> EF Core 6
.NET 7 ----> EF Core 7
.NET 8 ----> EF Core 8
.NET 9+ ----> EF Core correspondenteEm APIs modernas, a recomendação é clara:
- Use sempre a versão LTS mais recente
- Evite misturar diferentes versões (ex: EF 7 em .NET 8)
Usar a combinação adequada traz melhorias relevantes em:
- Performance de queries
- Tradução correta de LINQ → SQL
- Menor alocação de memória
- Melhor suporte a async e pooling
Instalação dos pacotes do Entity Framework Core
O EF Core é distribuído de forma modular. Em uma API ASP.NET Core, normalmente você precisa apenas desses dois pacotes essenciais.
Pacotes essenciais
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.DesignEntityFrameworkCore
Contém o runtime do ORM.EntityFrameworkCore.Design
Necessário para migrations, scaffolding e comandos dodotnet ef.
Esse pacote é usado apenas em tempo de desenvolvimento, mas costuma ser mantido no projeto da API por simplicidade.
DbContext lifetime: A unidade de trabalho da API
O DbContext representa uma unidade de trabalho. Ele mantém por debaixo dos panos:
- Conexão com o banco
- Change Tracker
- Transações
- Cache de entidades carregadas
Em APIs HTTP, a unidade de trabalho natural é a requisição. Por isso, o ciclo de vida correto do DbContext é Scoped (Unica instância de serviço por requisição).
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString));Conceitualmente:
- Cada request cria um DbContext
- Todas as operações daquele request compartilham o mesmo contexto
- Ao final, o contexto é descartado
Isso garante isolamento, previsibilidade e evita efeitos colaterais entre requisições. Usar DbContext como Singleton(Unica instância de serviço por execução da aplicação) ou reutilizá-lo fora desse escopo quebra completamente esse modelo.
Tracking vs NoTracking: Estado tem custo
O Change Tracking é um dos principais diferenciais do EF Core. Ele permite que o framework perceba as alterações automaticamente e gere comandos de persistência necessários.
var user = await _context.Users.FirstAsync();
user.Name = "Novo Nome";
await _context.SaveChangesAsync();Esse comportamento é essencial para operações de escrita, mas tem custo. Cada entidade trackeada consome memória e processamento.
Em APIs, a maioria dos endpoints é voltada para leitura. Nesses casos, o tracking não traz benefício algum.
var users = await _context.Users
.AsNoTracking()
.ToListAsync();A decisão aqui é conceitual e faz total diferença:
- Escrita precisa de monitoramento do estado das entidades
- Leitura não
Quando essa separação é respeitada, a aplicação se torna naturalmente mais eficiente.
Projeções com Select: Intenção explícita da API
Entidades representam o modelo interno da aplicação. APIs expõem contratos de dados. Misturar esses dois conceitos costuma gerar problemas.
Um erro comum:
return await _context.Users.ToListAsync();Isso:
- Retorna mais dados do que o necessário
- Expõe o modelo de persistência
- Cria acoplamento entre API e banco
Uma projeção adequada resolve isso de forma simples:
var users = await _context.Users
.AsNoTracking()
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email
})
.ToListAsync();
Aqui, a query expressa exatamente a intenção do endpoint.
Além de melhorar performance, a projeção melhora legibilidade e manutenção. o Entity Framework Core permite ainda, inspecionar o SQL e os log gerados em tempo de execução.
Ter os logs de queries habilitado é essencial para diagnosticar problemas de performance, validar traduções LINQ para SQL e evitar execuções inesperadas fora do banco de dados.
Problemas comuns de performance com EF Core
A maioria dos problemas de performance surge pelo uso de alguns padrões recorrentes usados de maneira errada.
Execução fora do banco
var users = _context.Users.ToList();
var activeUsers = users.Where(u => u.IsActive);
Nesse caso, o filtro ocorre em memória. O correto é delegar e passar o máximo de responsabilidade possível ao banco de dados, solicitando apenas o que é necessário.
var activeUsers = await _context.Users
.Where(u => u.IsActive)
.ToListAsync();
Include excessivo
_context.Orders
.Include(o => o.Customer)
.Include(o => o.Items)
.Include(o => o.Payments);Esse padrão gera SQL grande, frágil e pouco performático. Na maioria dos cenários, projeção é mais eficiente e mais clara.
Materialização precoce
Chamar ToList() cedo demais impede que o EF otimize a query.
A regra é simples: materialize os dados o mais tarde possível.
Code First: O código como fonte da verdade
Em APIs modernas, o banco de dados vá evoluindo junto com o código. O Code First transforma o modelo em fonte oficial do schema.
Onde a entidade modelo User através da utilização de migrations, se transforma em código SQL para criação da tabela User.
public class User
{
public Guid Id { get; set; }
public string Name { get; set; } = null!;
public string Email { get; set; } = null!;
public bool IsActive { get; set; }
}
dotnet ef migrations add CreateUsers
dotnet ef database updatepublic partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "text", nullable: false),
Email = table.Column<string>(type: "text", nullable: false),
IsActive = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}Migrations representam a história e evolução do sistema. Quando usadas com disciplina, eliminam divergências entre ambientes e reduzem retrabalho.
Conclusão
Entity Framework Core não é apenas uma ferramenta de acesso aos dados. Ele impõe um modelo de pensamento sobre como uma API deve interagir com o banco.
Quando usado com clareza de intenção, separando leitura de escrita, projetando dados corretamente e respeitando o ciclo de vida do DbContext ele deixa de ser um risco de performance e se torna um aliado sólido na construção de APIs modernas e aplicações cada vez mais rápidas.
Entity Framework não é lento. A sua utilização de forma incorreta é.
Código-fonte do projeto
Para facilitar o entendimento, disponibilizei o projeto completo com o exemplo de uso adequado do Entity Framework, já organizado com separação de responsabilidades e boas práticas:
👉 Baixar o projeto no GitHub neste link:
Quer ver mais conteúdos como este?
Se você trabalha com .NET, C# e backend, este blog seguirá trazendo conteúdos práticos e objetivos sobre o ecossistema Microsoft, com foco em código, boas práticas e decisões técnicas aplicadas ao mundo real.
Acompanhe para continuar aprofundando seu domínio sobre a plataforma .NET.