Dapper ou Entity Framework Core - Qual escolher?

Dapper ou Entity Framework Core? Se você já trabalhou com acesso a dados no .NET, com certeza já se fez esta pergunta.

Por isso, neste artigo vamos entender o que cada um faz por baixo dos panos, comparar o código dos dois e ver em quais cenários eu uso cada um aqui no balta.io.

Arte de capa com os logotipos lado a lado comparando Dapper ou Entity Framework Core para acesso a dados no .NET
Dois caminhos para o mesmo objetivo: transformar resultados do SQL em objetos C#.

Dapper ou Entity Framework Core: o que eles são

Tanto o Dapper quanto o Entity Framework Core são classificados como frameworks ORM (Object/Relational Mapping), ou seja, ferramentas de mapeamento objeto relacional.

Na verdade, o Dapper se classifica como um Micro ORM. Isso acontece porque ele é menor, tem menos funcionalidades e, consequentemente, fica mais leve.

Em ambos, a finalidade é facilitar as operações sobre bancos de dados relacionais, como CREATE, READ, UPDATE e DELETE. Em outras palavras, o famoso CRUD.

O SQL Server, assim como qualquer outro banco relacional, trabalha com uma linguagem diferente do C#. Então, o objetivo dos dois é converter o retorno do SQL para objetos C# e também o caminho inverso.

No caso do Entity Framework Core, existem várias outras facilidades, porque ele é bem mais robusto. Algumas delas até conseguimos agregando outras bibliotecas ao Dapper, mas esse não é o nosso foco aqui.

Em resumo, queremos executar operações no banco de dados com C# de uma forma fácil. Por isso, faz sentido comparar Dapper ou Entity Framework Core lado a lado.

O que Dapper ou Entity Framework Core fazem por baixo dos panos

Independente da sua escolha, o processo de conexão e de operação no banco de dados é sempre o mesmo. Na prática, o Dapper ou Entity Framework Core apenas nos entregam uma abstração desse processo.

Portanto, antes de comparar as bibliotecas, vale a pena montar esse fluxo na mão, usando apenas o ADO.NET.

Connection string

A connection string define como vamos nos conectar ao banco de dados. Além disso, ela informa o servidor, o usuário, o banco e outros recursos de segurança.

Caso ainda não tenha o SQL Server instalado, recomendo seguir este tutorial de SQL Server com Docker. Para entender cada parâmetro, também vale ler o artigo que descomplica as connection strings do SQL Server.

Para estes exemplos, vamos usar como base uma connection string simples, a mesma dos cursos do balta.io.

const string connectionString = "Server=localhost,1433;Database=balta;User ID=sa;Password=1q2w3e4r@#$";

Microsoft.Data.SqlClient

Embora o .NET não traga uma biblioteca de acesso ao SQL Server já inclusa, a Microsoft disponibiliza o Microsoft.Data.SqlClient. Trata-se do pacote oficial para conexão e operações no SQL Server.

Para adicionar esse pacote, basta executar o comando abaixo.

dotnet add package Microsoft.Data.SqlClient

Esse pacote é a base para conexões ao SQL Server, e tanto o Dapper quanto o Entity Framework Core dependem dele. No caso do Entity Framework Core, ele chega automaticamente junto com o provider que vamos ver mais adiante.

Conectando ao banco e executando comandos

Com o pacote instalado, precisamos nos conectar ao banco antes de qualquer operação. Para isso, usamos a classe SqlConnection.

using (var connection = new SqlConnection(connectionString))
{
    // operações no banco
}

Note que usamos o using para fazer o Dispose automático desse objeto. Afinal, não queremos ninguém conectado ao banco de dados mais do que o necessário.

Segundo a documentação oficial do SqlConnection, uma conexão que sai de escopo não é fechada sozinha. Por isso, o using garante que ela volte para o pool de conexões ao fim do bloco.

Ficou confuso sobre o Dispose? Então dá uma conferida no nosso curso Fundamentos do C#, que lá explico tudo com detalhes.

Com a conexão criada, podemos abri-la e executar qualquer comando no SQL Server. Para isso, usamos o SqlCommand.

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();

    using (var command = new SqlCommand())
    {
        command.Connection = connection;
        command.CommandType = System.Data.CommandType.Text;
        command.CommandText = "SELECT [Id], [Title] FROM [Category]";

        // execução do comando
    }
}

SqlDataReader

Pergunta de entrevista: qual a forma mais rápida de ler dados do SQL Server via .NET e C#?

A resposta é o SqlDataReader, um cursor forward only, ou seja, que só anda para frente. Tanto o Dapper quanto o Entity Framework Core usam esse leitor para buscar os dados.

De acordo com a Microsoft, o DataReader devolve os resultados enquanto a consulta executa. Além disso, por padrão ele mantém apenas uma linha por vez em memória, o que reduz o consumo de recursos.

O método ExecuteReader retorna um SqlDataReader, e então podemos iterar por ele.

var reader = command.ExecuteReader();
while (reader.Read())
{
    Console.WriteLine($"{reader.GetGuid(0)} - {reader.GetString(1)}");
}

O código final desse método fica assim.

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();

    using (var command = new SqlCommand())
    {
        command.Connection = connection;
        command.CommandType = System.Data.CommandType.Text;
        command.CommandText = "SELECT [Id], [Title] FROM [Category]";

        var reader = command.ExecuteReader();
        while (reader.Read())
        {
            Console.WriteLine($"{reader.GetGuid(0)} - {reader.GetString(1)}");
        }
    }
}

Por que isso importa na escolha entre Dapper ou Entity Framework Core

Como você pode notar, escrevemos bastante código para fazer uma simples leitura. Em uma escala maior, isso vira um problema, então eu recomendaria esse método apenas em casos de extrema necessidade de performance.

Porém, entender esse fluxo ajuda a enxergar o ponto mais importante sobre os ORMs: a finalidade deles.

Basicamente, o que o Dapper ou Entity Framework Core fazem é executar esse SqlDataReader, nos casos de leitura. Em seguida, eles convertem o resultado para um objeto do C#.

Então, dada uma query como esta:

SELECT [Name], [Age], [Salary] FROM [Customer]

O Dapper ou Entity Framework Core conseguiriam converter o SqlDataReader para o seguinte objeto.

public class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
    public decimal Salary { get; set; }
}

Entender SQL é fundamental para qualquer pessoa desenvolvedora. Caso precise de ajuda, veja nosso curso de Fundamentos do SQL Server.

Começando com Dapper

Particularmente, gosto bastante do Dapper pela simplicidade e pela performance. Ele nasceu no time do Stack Overflow e hoje segue como projeto open source, mantido pela organização DapperLib no repositório oficial do Dapper.

Para usar o Dapper, basta adicionar o pacote Dapper, como mostrado abaixo.

dotnet add package Dapper

O Dapper também exige um SqlConnection. No entanto, ele reduz muito o nosso código, porque adiciona métodos de extensão a esse objeto.

using (var connection = new SqlConnection(connectionString))
{
    var query = "SELECT [Name], [Age], [Salary] FROM [Customer]";
    var customers = connection.Query<Customer>(query);
}

O Dapper adicionou a extensão Query<T> ao SqlConnection. Com isso, fazemos o parse do resultado do SQL Server para um objeto C# de forma extremamente simples.

Ficou com dúvidas sobre parse, generics e outros assuntos de OOP? Então faça nosso curso de Fundamentos da Orientação a Objetos.

A mágica toda acontece porque a query retorna os mesmos campos que temos como propriedades no objeto Customer. Se mudássemos Name para FullName apenas na query ou apenas na classe, essa propriedade não seria preenchida.

Além do Query<T>, o Dapper oferece o Execute para comandos de escrita e o QueryMultiple para ler vários resultados em uma única ida ao banco. Se quiser ver um exemplo completo, confira este artigo sobre como criar um CRUD com Dapper.

Dapper Contrib

Ainda podemos simplificar esse código com um pacote adicional, chamado Dapper.Contrib.

dotnet add package Dapper.Contrib

O Dapper.Contrib adiciona ainda mais extensões ao SqlConnection e gera até a query para nós. Isso mesmo, não precisamos escrever o SQL.

using (var connection = new SqlConnection(connectionString))
{
    var customers = connection.GetAll<Customer>();
}

Novamente, a mágica acontece porque a tabela tem os mesmos campos que as propriedades da classe. Assim, ele gera um SELECT com base nesse mapeamento.

Atenção a um detalhe: por convenção, o Contrib procura a tabela pelo nome da classe no plural. Portanto, para a tabela Customer no singular, decore a classe com o atributo [Table("Customer")].

O mesmo vale para os outros métodos.

connection.GetAll<Customer>();
connection.Get<Customer>(id);
connection.Insert(customer);
connection.Update(customer);
connection.Delete(customer);

Além disso, o Contrib usa por padrão uma propriedade chamada Id como chave. Quando a chave é gerada pela aplicação, como um Guid, use o atributo [ExplicitKey].

Senhoras e senhores, isso é boa parte do que o Dapper, o Entity Framework Core e qualquer outro ORM fazem por nós!

Começando com Entity Framework Core

O Entity Framework Core trabalha de uma forma diferente do Dapper. Ele precisa de um pouco mais de configuração, porém entrega muito mais "mágica".

Para trabalhar com Entity Framework Core e SQL Server, precisamos adicionar dois pacotes.

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Data context

O Entity Framework Core trabalha com um conceito chamado data context, ou contexto de dados. Ele representa o banco de dados transportado para os nossos objetos.

Em suma, o data context é apenas uma classe que concentra as tabelas que queremos mapear.

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

    public DbSet<Customer> Customers { get; set; }
}

Desta forma, podemos ter N objetos na aplicação, mas o Entity Framework Core cuida apenas dos tipos informados nos DbSet. Neste exemplo, ele cuida apenas do Customer.

Conexão

O Entity Framework Core também se diferencia do Dapper na criação e no gerenciamento da conexão com o banco.

Normalmente, nas aplicações ASP.NET Core, configuramos o Entity Framework Core no método ConfigureServices, com um código similar a este.

public void ConfigureServices(IServiceCollection services)
{
    // demais serviços
    services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer("CONNECTION_STRING"));
}

Isso é tudo que precisamos configurar para começar. Porém, recomendo também trabalhar no mapeamento das entidades, entre vários outros itens que não vamos cobrir aqui.

Vale saber que o AddDbContext registra o contexto como scoped. Ou seja, cada requisição HTTP recebe a sua própria instância, que o container descarta no fim da requisição.

Operações com Entity Framework Core

Embora a configuração inicial seja mais complexa, o Entity Framework Core traz facilidades que influenciam muito a produtividade.

// Lendo todos os registros
var customers = context.Customers.ToList();

// Lendo um registro
var customer = context.Customers.FirstOrDefault(x => x.Id == id);

// Criando um registro
context.Customers.Add(customer);
context.SaveChanges();

// Atualizando um registro
context.Customers.Update(customer);
context.SaveChanges();

// Removendo um registro
context.Customers.Remove(customer);
context.SaveChanges();

Unit of work: diferença entre Dapper ou Entity Framework Core

Você deve ter notado o uso do context.SaveChanges() no código acima. Isso acontece porque o Entity Framework Core trabalha com um padrão chamado unit of work, ou unidade de trabalho.

Enquanto não executamos o SaveChanges(), nada vai para o banco. Em outras palavras, temos uma espécie de transação acontecendo aqui.

De fato, a documentação do EF Core confirma que, por padrão, todas as mudanças de uma única chamada ao SaveChanges rodam em uma transação. Se uma delas falhar, nenhuma alteração chega ao banco.

No Dapper, esse controle fica por nossa conta. Se quiser ver como implementar isso, leia o artigo sobre Dapper com Unit of Work e Repository Pattern.

LINQ no Entity Framework Core

Até então, nada especial com o Entity Framework Core, correto? Mas aí entra um recurso muito legal dele: as queries usando LINQ.

Depois de mapear e referenciar as entidades, podemos escrever consultas em LINQ. Então, o Entity Framework Core traduz essas consultas para a linguagem SQL.

Quer aprender mais sobre LINQ, listas e OOP? Então faça nosso curso de Fundamentos da Orientação a Objetos.

Por exemplo, podemos carregar todos os produtos com as suas respectivas categorias sem muito esforço.

var products = context.Products.Include(x => x.Category).ToList();

Isso resulta em uma query similar a esta.

SELECT
    * -- usado apenas como exemplo
FROM
    [Product]
    INNER JOIN [Category]
        ON [Product].[CategoryId] = [Category].[Id]

Também podemos ir além e executar queries mais complexas. Por exemplo, carregar pedidos que possuem vários itens, sendo que cada item possui um produto.

var orders = context
    .Orders
    .Include(x => x.Lines)
    .ThenInclude(x => x.Product)
    .ToList();

O mesmo vale para a sintaxe de consulta, sem lambda expressions.

var orders = from order in context.Orders select order;

Um cuidado importante: a escolha entre IEnumerable e IQueryable muda o SQL gerado. Este artigo explica como a interface impacta a tradução de SQL no EF.

SQL puro ou LINQ com Dapper ou Entity Framework Core

Outro ponto a ponderar é o uso excessivo tanto de SQL quanto de LINQ. Embora o LINQ traga benefícios como produtividade e erros em tempo de compilação, há cenários em que a sintaxe SQL claramente se destaca.

Então, o segredo é ponderar o uso. Se você tenta replicar uma consulta SQL via LINQ e não consegue, que tal executar um SQL puro?

Aliás, o Entity Framework Core também permite executar SQL cru, as chamadas raw queries. Para consultas que retornam entidades, usamos o FromSqlRaw ou o FromSqlInterpolated sobre o DbSet.

var customers = context.Customers
    .FromSqlInterpolated($"SELECT * FROM [Customer] WHERE [Age] > {minAge}")
    .ToList();

Para comandos que não retornam dados, como um UPDATE, usamos o context.Database.ExecuteSqlRaw. Contudo, a documentação da Microsoft alerta para o risco de SQL injection nos métodos "Raw", então sempre passe os valores como parâmetros.

Não se limite a uma única visão. Saiba como Dapper ou Entity Framework Core funcionam e tire o melhor proveito de cada um em cada situação.

Dapper ou Entity Framework Core: qual devo utilizar?

Mas e aí, no resumão, compensa usar Dapper ou Entity Framework Core? Essa é uma pergunta que eu responderia com "depende", mas vou deixar algumas dicas a partir do que usamos aqui no balta.io.

Cenários pequenos e de leitura

Em diversos cenários menores, prefiro o Dapper. Por exemplo, nas Azure Functions que tenho aqui no balta.io, preciso apenas fazer um SELECT em uma tabela e retornar os dados.

As referências dele são simples e leves, e o pacote é bem polido. Para quase nenhuma escrita, ele é perfeito.

Backoffice e produtividade

Já no contexto de backoffice, com cadastro de cursos, categorias, artigos e afins, uso massivamente o Entity Framework Core.

Ele me dá toda a produtividade de que preciso, já que o backoffice recebe acesso de apenas três a cinco usuários. Além disso, a integração do Visual Studio com o Entity Framework Core gera telas de forma automática e poupa ainda mais tempo.

No cenário de conteúdo, que inclui o site e o player onde vocês assistem às aulas, também uso o Entity Framework Core, por pura comodidade.

Na época em que escrevi a versão original deste texto, tínhamos em torno de 30 mil alunos e cerca de 7 mil conexões simultâneas. O Entity Framework Core dava conta disso tranquilamente.

Na balança performance versus produtividade, pesamos para o lado da produtividade naquele momento.

Telas com muitas consultas: Dapper ou Entity Framework Core?

Ainda existe um terceiro cenário, que é uma tela apenas: a home do player. Nela, preciso carregar cursos, carreiras e vários itens do aluno ao mesmo tempo.

Nesse caso, uso o QueryMultiple do Dapper e faço a leitura de uma view do SQL Server. Foi a forma mais otimizada que encontrei para resolver as diversas queries que essa tela precisa.

Na época, a ideia era migrar essa query para o Entity Framework Core com a chegada do Entity Framework Core 6. O motivo seria apenas termos mais necessidade do Entity Framework Core do que do Dapper, e não porque o Dapper seja ruim.

Se o seu critério principal for velocidade, vale conferir este comparativo de performance entre EF Core e Dapper.

Por fim, mas não menos importante: se você está começando, o Dapper é a sua porta de entrada. Como ele abstrai menos coisas, você precisa aprender mais sobre acesso a dados.

Inclua também na sua lista aprender SQL. Sim, ele é extremamente importante, e muitas pessoas negligenciam isso.

Basicamente, todo sistema se conecta a um banco de dados. Embora os bancos NoSQL sejam fantásticos, a probabilidade de você trabalhar com um banco relacional é muito maior.

Conclusão sobre Dapper ou Entity Framework Core

Dapper ou Entity Framework Core não são rivais, e sim ferramentas para momentos diferentes. Ambos usam o mesmo SqlDataReader por baixo dos panos e entregam objetos C# no final.

O Dapper brilha quando você quer controle total do SQL, pouca abstração e leituras rápidas. Por outro lado, o Entity Framework Core se destaca quando produtividade, LINQ e unidade de trabalho pesam mais na balança.

Sabendo bem os fundamentos, o acesso a dados, o Dapper e o Entity Framework Core, você está preparado para o que der e vier.

FAQ

O Dapper é mais rápido que o Entity Framework Core?

Em geral, o Dapper tem menos sobrecarga, porque apenas mapeia o resultado do SQL para objetos. Ainda assim, a diferença depende da consulta, e o EF Core pode ficar bem próximo com consultas sem rastreamento.

Posso usar Dapper e Entity Framework Core no mesmo projeto?

Sim. É comum usar o EF Core para escrita e cadastros e o Dapper para leituras complexas ou telas com muitas consultas.

O Dapper gera o SQL para mim?

O Dapper puro não gera SQL, você escreve as queries. O pacote Dapper.Contrib, por outro lado, gera comandos simples de CRUD a partir das classes.

Como executar SQL puro no Entity Framework Core?

Use FromSqlRaw ou FromSqlInterpolated no DbSet para consultas e Database.ExecuteSqlRaw para comandos. Sempre parametrize os valores para evitar SQL injection.

Qual dos dois devo aprender primeiro?

Para quem está começando, o Dapper ensina mais sobre SQL e acesso a dados. Depois disso, o EF Core fica bem mais fácil de entender.