Meça a jornada, não apenas uma linha de código
Registre duração, quantidade de consultas, linhas retornadas e esperas relevantes em um cenário realista. Verifique a consulta gerada pelo EF Core e o plano de execução no banco. Logs de comandos podem expor dados sensíveis; configure parâmetros e logging com cuidado, especialmente em produção.
Compare a mesma operação com volume e distribuição próximos aos reais. Uma consulta rápida com dez linhas pode degradar com milhões; benchmark isolado não substitui métricas de produção e análise de plano.
Evite trazer dados desnecessários
Para leitura, projete apenas colunas utilizadas em vez de materializar agregados inteiros. Se não há alteração a salvar, AsNoTracking reduz trabalho de change tracking. Não aplique indiscriminadamente: tracking é necessário quando a unidade de trabalho precisa persistir as mudanças e pode oferecer identidade consistente dentro do contexto.
var resultado = await db.Pedidos
.AsNoTracking()
.Where(p => p.ClienteId == clienteId)
.OrderByDescending(p => p.CriadoEm)
.Select(p => new PedidoResumo(p.Id, p.CriadoEm, p.Total))
.Take(50)
.ToListAsync(cancellationToken);Detecte e corrija N+1
N+1 ocorre quando uma consulta busca uma coleção principal e o código dispara uma consulta adicional para cada item. Carregamento lazy pode tornar isso pouco visível. Inspecione quantidade e padrão dos comandos; prefira projeção ou carregamento explícito adequado ao caso.
Carregar várias coleções com joins pode multiplicar linhas e transferir dados duplicados. Compare projeção, split queries e consultas específicas, considerando consistência e round trips. “Eager loading em tudo” também pode ser uma regressão.
Paginação e índices
Paginação por offset é simples e funciona bem para páginas pequenas, mas offsets grandes podem exigir percorrer muitas linhas e os resultados podem mudar entre páginas. Em feeds grandes, keyset pagination usa a última chave vista como cursor, com ordenação estável e índice compatível.
Índices aceleram leituras específicas ao custo de espaço e trabalho adicional em inserções/atualizações. Escolha com base em filtros, ordenação, seletividade e plano de execução. Mais índices nem sempre significam banco mais rápido.
Transações, conexões e concorrência
Consultas lentas podem manter conexões e locks por mais tempo. Use transações com escopo apropriado e evite trabalho de rede enquanto mantém transação aberta. Reveja pool de conexões, concorrência e padrões de acesso antes de adicionar cache; Redis introduz decisões de invalidação e consistência que devem ser justificadas por métricas.
Um roteiro de investigação
- Identifique endpoint, percentis de latência e período do problema.
- Correlacione traces e logs com as consultas executadas.
- Analise SQL e plano real; confirme linhas estimadas versus observadas.
- Forme uma hipótese pequena: índice, projeção, paginação ou eliminação de N+1.
- Meça novamente sob carga e acompanhe possíveis regressões.
Para comparar fornecedores e comportamento de banco, veja SQL Server e PostgreSQL para desenvolvedores .NET. Para instrumentação, veja observabilidade. O guia prático de performance em .NET conecta esse diagnóstico a benchmarks, runtime e validação sob carga.