Entity Framework ou Dapper? Eu me recuso a escolher um time

Entity Framework Core e Dapper aparecem, com uma frequência estranha, como se fossem times opostos torcendo contra o outro. Só que os dois resolvem o mesmo problema geral, aplicação C# conversando com banco de dados relacional, partindo de decisões arquiteturais bem diferentes, e isso os torna complementares com muito mais frequência do que concorrentes.

O que EF Core realmente faz por trás do LINQ

Entity Framework Core é um ORM completo: mapeia classes C# para tabelas, rastreia mudanças em entidades carregadas (change tracking), traduz consultas LINQ para SQL antes de qualquer execução, e gerencia esquema de banco via migrations.

O change tracking é a peça que mais diferencia EF Core de uma consulta SQL crua. Quando uma entidade é carregada através do DbContext, o EF Core guarda um snapshot do estado original dela. Ao chamar SaveChanges(), ele compara o estado atual com esse snapshot e gera automaticamente os comandos UPDATE necessários, sem que seja preciso escrever SQL de atualização manualmente.

var pedido = await dbContext.Pedidos.FindAsync(id);
pedido.Status = StatusPedido.Enviado;
await dbContext.SaveChangesAsync();

Nenhum SQL explícito de update aparece nesse trecho. O EF Core descobre sozinho o que mudou.

O que Dapper faz, e o que ele deliberadamente não faz

Dapper é um micro-ORM: uma camada fina sobre ADO.NET, que mapeia o resultado de uma consulta SQL para objetos C#, sem gerar SQL, sem change tracking e sem tradução de LINQ. O SQL é escrito à mão, como string, e o Dapper cuida apenas do trabalho de converter linhas do DataReader em instâncias de classe:

using var connection = new SqlConnection(connectionString);
var pedidos = await connection.QueryAsync<Pedido>(
    "SELECT Id, Status, Total FROM Pedidos WHERE ClienteId = @ClienteId",
    new { ClienteId = clienteId });

Não existe abstração escondendo o que está sendo executado no banco. O que está na string SQL é exatamente o que roda.

Onde cada abordagem se destaca

EF Core tende a compensar melhor em cenários de escrita orientada a domínio, com regra de negócio complexa, várias entidades relacionadas mudando juntas, e onde ter as mudanças rastreadas automaticamente evita escrever manualmente cada UPDATE e INSERT. Migrations também ajudam a manter o esquema do banco versionado junto do código.

Dapper tende a se destacar em consulta de leitura massiva, relatório, tela de listagem com filtro variado e cenário onde o overhead de tradução de LINQ e rastreamento de mudança, que a aplicação nem vai usar já que é só leitura, representa custo desnecessário. Como o SQL é escrito diretamente, também fica mais fácil aproveitar recurso específico do banco que uma tradução genérica de ORM poderia não expor tão diretamente.

Por que “escolher um time” é a pergunta errada

Nada impede as duas abordagens de conviverem na mesma aplicação. Um padrão comum é usar EF Core no lado de escrita, aproveitando change tracking e regra de domínio, e Dapper no lado de leitura, especialmente em consulta de relatório ou listagem onde performance pura importa mais que conveniência de mapeamento automático.

A pergunta relevante nunca foi “qual ORM é melhor” de forma abstrata. É “essa consulta ou essa operação específica se beneficia mais de rastreamento automático de mudança, ou de controle direto sobre o SQL executado”. As duas respostas podem ser verdadeiras na mesma aplicação, em partes diferentes dela.