> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Dapper: Arquitetura interna, performance e boas práticas
- URL: https://www.azurebrasil.cloud/blog/dapper-arquitetura-interna-performance-e-boas-praticas/
- Published: 2026-03-06T12:00:18.000Z
- Updated: 2026-03-06T14:07:39.000Z
- Description: Uma visão técnica sobre como Dapper opera com PostgreSQL no .NET, explorando performance, estrutura interna e boas práticas de implementação.
- Author: Charlison Fideles de Oliveira

O Dapper é um micro-ORM mantido pela equipe do [Stack Overflow](https://stackoverflow.com/?ref=azurebrasil.cloud), criado para resolver um problema real de produção: executar consultas SQL de forma extremamente performática, reduzindo o custo de mapeamento manual do ADO.NET sem introduzir aquela complexidade que tem um ORM completo.

É importante entender que o Dapper **não é uma versão simplificada do** [**Entity Framework**](https://learn.microsoft.com/vi-vn/ef/core/?ref=azurebrasil.cloud), material que ja vimos [neste artigo](https://www.azurebrasil.cloud/blog/entity-framework-em-apis-modernas/). Ele é uma ferramenta com uma finalidade diferente.

Enquanto o Entity Framework Core tenta abstrair o banco através de:

- Change tracking
- Tradução de [LINQ](https://learn.microsoft.com/pt-br/dotnet/csharp/linq/?ref=azurebrasil.cloud)
- Model metadata
- Pipeline de compilação de queries
- Geração automática de SQL

O Dapper assume que:

> Você quer escrever SQL manualmente e apenas precisa de um materializador rápido e confiável.

Ele opera diretamente sobre **IDataReader**, sendo essencialmente um mapeador de resultados para objetos POCO(em outras palavras, classes simples com propriedades) com uso intensivo de geração dinâmica de IL(Linguagem intermediária) e cache interno.

# Arquitetura e funcionamento interno

## Dependência central: IDbConnection

O Dapper não cria conexão, não gerencia pool e não mantém contexto. Ele adiciona extension methods à interface:

```csharp
System.Data.IDbConnection
```

Isso significa que:

- Ele funciona com qualquer provider [ADO.NET](https://learn.microsoft.com/pt-br/dotnet/framework/data/adonet/ado-net-overview?ref=azurebrasil.cloud)
- Ele delega o gerenciamento de conexão ao driver
- Ele não impõe modelo arquitetural

Essa decisão reduz acoplamento e preserva performance.

## Fluxo interno de execução

Quando executamos:

```csharp
connection.Query<User>("SELECT id, name FROM users");
```

Internamente ocorre:

1. Criação de **IDbCommand**
2. Binding de parâmetros
3. Execução de **ExecuteReader**
4. Leitura via IDataReader
5. Geração dinâmica de método de materialização
6. Cache do delegate gerado
7. Iteração e retorno da coleção

O ponto crítico está na etapa 5.

## Geração dinâmica via IL

O Dapper usa **DynamicMethod** e **ILGenerator** para construir um método em tempo de execução equivalente a:

```csharp
var user = new User();
user.Id = reader.GetGuid(0);
user.Name = reader.GetString(1);
return user;
```

Esse delegate é compilado uma única vez e armazenado em cache.

Resultado:

- Primeira execução: pequeno overhead
- Execuções subsequentes: desempenho próximo ao ADO.NET puro

Isso elimina o custo repetitivo de um reflection tradicional.

# Integração com PostgreSQL

Para nosso exemplo didático iremos utilizar o Dapper com PostgreSQL, para isso, utilizaremos o provider oficial [Npgsql](https://www.npgsql.org/?ref=azurebrasil.cloud) que é o driver ADO.NET oficial para PostgreSQL.

Ele fornece:

- Pooling de conexão
- Async real
- Suporte a UUID
- Suporte a JSONB
- Arrays nativos
- Prepared statements

Instalação

```csharp
dotnet add package Dapper
dotnet add package Npgsql
```

## Connection String

```json
{
  "ConnectionStrings": {
    "Default": "Host=localhost;Port=5432;Database=appdb;Username=postgres;Password=123456;Pooling=true;Minimum Pool Size=5;Maximum Pool Size=100"
  }
}
```

Para o exemplo atual o pool e controlado exclusivamente pelo Npgsql.

# Implementação com Minimal API + PostgreSQL

## Program.cs

```csharp
using Dapper;
using Npgsql;
using System.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IDbConnection>(sp =>
{
    var configuration = sp.GetRequiredService<IConfiguration>();
    var connectionString = configuration.GetConnectionString("Default");

    return new NpgsqlConnection(connectionString);
});

builder.Services.AddScoped<UserRepository>();

var app = builder.Build();

app.MapGet("/users/{id:guid}", async (Guid id, UserRepository repo) =>
{
    var user = await repo.GetByIdAsync(id);
    return user is null ? Results.NotFound() : Results.Ok(user);
});

app.MapPost("/users", async (CreateUserRequest request, UserRepository repo) =>
{
    var id = await repo.CreateAsync(request);
    return Results.Created($"/users/{id}", new { id });
});

app.Run();
```

Repository

```csharp
public class UserRepository
{
    private readonly IDbConnection _connection;

    public UserRepository(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<Guid> CreateAsync(CreateUserRequest request)
    {
        var id = Guid.NewGuid();

        const string sql = """
            INSERT INTO users (id, name, email)
            VALUES (@Id, @Name, @Email)
        """;

        await _connection.ExecuteAsync(sql, new
        {
            Id = id,
            request.Name,
            request.Email
        });

        return id;
    }

    public async Task<User?> GetByIdAsync(Guid id)
    {
        const string sql = """
            SELECT id, name, email
            FROM users
            WHERE id = @Id
        """;

        return await _connection
            .QueryFirstOrDefaultAsync<User>(sql, new { Id = id });
    }
}
```

# Comparação com EF Core

## Pipeline interno

### EF Core

1. Expressão LINQ
2. Árvore de expressão
3. Compilação de query
4. Geração de SQL
5. Execução
6. Materialização
7. Change tracking

### Dapper

1. SQL manual
2. Execução direta
3. Materialização via IL

## Comparativo estrutural

```
Comparativo Estrutural - Dapper vs EF Core

Aspecto			Dapper			EF Core
Performance		Muito alta		Alta
Change Tracking		Não			Sim
SQL automático		Não			Sim
LINQ			Não			Sim
Overhead de memória	Baixo			Médio
Controle de SQL		Total			Parcial
```

## Quando usar EF Core

- Domínio rico
- Agregados complexos
- Necessidade de change tracking
- Produtividade maior que performance

## Quando usar Dapper

- Alta taxa de leitura
- Queries complexas
- Relatórios
- Microservices
- Cenários CQRS (lado de leitura)

# Erros comuns de utilização

Apesar de ser simples conceitualmente, o Dapper exige um certo nível de maturidade técnica. A maior parte dos problemas em produção não está no Dapper em sí, mas na forma como ele é utilizado. Veremos quais são os erros mais recorrentes que serão explicados baseados em uma visão arquitetural e operacional.

## Falta de controle explícito sobre SQL e segurança

O Dapper não protege você contra más decisões de escrita de SQL. Ele apenas executa o que você fornece.

Um erro extremamente comum é a concatenação de string para construção de query:

```csharp
var sql = $"SELECT * FROM users WHERE email = '{email}'";
```

Esse padrão de utilização expõe o sistema a SQL Injection por exemplo. Como o Dapper não faz parsing nem validação semântica da query, ele simplesmente envia o texto ao provider (por exemplo, via Npgsql) que fica responsável por executar a instrução.

A forma correta é utilizar parâmetros:

```csharp
connection.Query<User>(
    "SELECT * FROM users WHERE email = @Email",
    new { Email = email }
);
```

Quando usamos parâmetros:

- O provider cria DbParameter
- O valor não é concatenado na string
- O banco trata como valor tipado
- O plano de execução pode ser reutilizado

Além de segurança, isso melhora cache de plano no PostgreSQL.

### Impacto rquitetural

Se sua equipe não domina SQL parametrizado, o Dapper se torna perigoso. Ele não possui camadas de proteção como ORMs que forçam parametrização implicitamente.

### Má gestão do ciclo de vida da conexão

O Dapper depende de **IDbConnection**. Ele não controla pool de conexões, nem estado, nem concorrência.

Erros comuns:

- Criar conexão como singleton
- Manter conexão aberta indefinidamente
- Abrir manualmente e esquecer de fechar
- Compartilhar conexão entre múltiplas threads

Exemplo problemático:

```csharp
builder.Services.AddSingleton<IDbConnection>(
    new NpgsqlConnection(connectionString)
);
```

Isso pode causar:

- Exceções de concorrência
- Estado inválido da conexão
- Vazamento de recursos
- Deadlocks

O correto é usar ***scoped*** ou criar por operação:

```csharp
builder.Services.AddScoped<IDbConnection>(sp =>
{
    var cs = sp.GetRequiredService<IConfiguration>()
               .GetConnectionString("Default");

    return new NpgsqlConnection(cs);
});
```

O pool é gerenciado pelo Npgsql. Cada instância de `NpgsqlConnection` é leve e representa um handle lógico ao pool.

### Impacto Arquitetural

Em ambientes de alta concorrência (API com centenas de requisições por segundo), uma má configuração de conexão pode gerar gargalos maiores que qualquer overhead de ORM.

### Acreditar que o Dapper resolve problemas de performance

Outro erro comum é supor que apenas trocar de Entity Framework Core para Dapper vai resolver problemas de lentidão do ambiente.

Se uma query está lenta, normalmente o problema está em:

- Índice inexistente
- Índice inadequado
- Plano de execução ruim
- JOIN mal estruturado
- Falta de análise via `EXPLAIN ANALYZE`

O Dapper executa exatamente o SQL que você escreveu. Ele não reescreve query, não adiciona otimizações e não cria índices.

Se você fizer:

```sql
SELECT * FROM large_table WHERE non_indexed_column = @value;
```

Será lento com Dapper, EF, ADO.NET ou qualquer outra ferramenta que você utilizar.

### Impacto Arquitetural

Dapper transfere totalmente a responsabilidade de performance para o desenvolvedor e para o banco.

Ele elimina overhead de abstração, mas expõe a qualidade real do seu SQL.

### Problemas de mapeamento e conversão de nomes

No caso em que usamos PostgreSQL normalmente utiliza `snake_case`. Já o padrão C# é `PascalCase`.

Classe:

```csharp
public string UserName { get; set; }
```

Coluna:

```SQL
user_name
```

Se você não configurar corretamente, o Dapper não conseguirá mapear. 

Uma das soluções e aplicar o Alias explicito:

```SQL
SELECT user_name AS "UserName"
```

Ou mesmo uma configuração definida globalmente:

```csharp
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
```

Esse detalhe costuma gerar bugs perigosos e silenciosos, onde propriedades retornam ***null*** como valor sem erro explícito.

### Impacto arquitetural

Em sistemas grandes, inconsistência de naming pode gerar falhas difíceis de rastrear. Dapper não faz validação automática de mapeamento.

### Uso incorreto de multi-mapping

Ao fazer JOIN, o Dapper precisa saber onde dividir os objetos retornados.

Exemplo:

```csharp
connection.Query<User, Role, User>(
    sql,
    (user, role) =>
    {
        user.Role = role;
        return user;
    },
    splitOn: "Id"
);
```

Se `splitOn` estiver incorreto:

- Objetos podem ser preenchidos parcialmente
- Propriedades podem ser sobrescritas
- Dados podem ser inconsistentes

O Dapper não infere automaticamente relacionamento como um ORM completo faria.

## Ausência de controle transacional explicito

O Dapper não implementa Unit of Work. Ele não controla as transações automaticamente.

Erro comum:

- Executar múltiplos comandos dependentes sem transação

Exemplo problemático:

```csharp
await connection.ExecuteAsync(sql1);
await connection.ExecuteAsync(sql2);
```

Se `sql2` falhar, o primeiro comando já foi persistido.

Forma correta:

```csharp
await connection.OpenAsync();
using var transaction = connection.BeginTransaction();

try
{
    await connection.ExecuteAsync(sql1, param1, transaction);
    await connection.ExecuteAsync(sql2, param2, transaction);

    transaction.Commit();
}
catch
{
    transaction.Rollback();
    throw;
}
```

# Performance e Escalabilidade

Com PostgreSQL via Npgsql:

- Pooling já otimizado
- Async real reduz thread blocking
- Prepared statements melhoram desempenho
- JSONB permite consultas estruturadas

O gargalo raramente será o Dapper.  
Normalmente será:

- Banco mal indexado
- Pool mal configurado
- I/O de rede

# Conclusão

Dapper é uma ferramenta de precisão.

Ele não veio e nem tenta substituir um ORM completo. Ele remove camadas de abstração para entregar:

- Previsibilidade
- Performance
- Controle total sobre SQL
- Baixo overhead

Com PostgreSQL via Npgsql, essa combinação é extremamente eficiente para:

- APIs de alta concorrência
- Sistemas multiempresa
- Microservices
- Dashboards
- Serviços de leitura intensiva

A escolha entre Dapper e EF Core não é ideológica ela é puramente arquitetural.

Em muitos sistemas modernos, a abordagem híbrida é a mais utilizada e eficiente:

- EF Core para escrita e domínio
- Dapper para leitura otimizada

Isso permite aproveitar o melhor dos dois mundos sem sacrificar performance ou produtividade.

## Código-fonte do projeto

Para facilitar o entendimento, disponibilizei o projeto completo com o exemplo de uso adequado do **Dapper**, já organizado com separação de responsabilidades e boas práticas:

👉 **Baixar o projeto no GitHub** [**neste link**](https://github.com/charlisonOliveira/MinhaMinimalApiDapper-BLOG?ref=azurebrasil.cloud)**:**

## 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**.