Dapper - Em detalhes

O Dapper é um micro ORM para .NET. Um mecanismo, simples, que auxilia no mapeamento de objetos a partir de consultas SQL. É uma library de alto desempenho para acesso a dados. Criado pela equipe do StackOverflow. Open Source. A palavra chave do Dapper é performance.

ORM's como Entity Framework ou NHibernate geram código SQL. O EF através do LINQ expression trees. Já outras frameworks utilizam DSL especifica, que está diretamente ligado ao ORM.
As coisas vão bem até precisar de consultas mais elaboradas, saindo do básico.
Quanto maior a complexidade do cenário, pior a consulta gerada. Impactando diretamente na performance do banco de dados. Deixando DBA's de cabelo em pé.

Projeto demo

Veja a aplicação construída para este artigo. Ela contém todas as queries e mais algumas.

Como surgiu o Dapper

O Dapper foi escrito por Marc Gravel e Sam Saffron em 2011, enquanto trabalhavam no StackOverflow. Ele surgiu para resolver os problemas de performance que o StackOverflow estava enfrentando. Muitos desses problemas relacionados a indexação do Google e claro ao LINQ-2-SQl, que estava onerando a performance. Foi diante desse cenário que surgiu o dapper. Confira mais detalhes neste post: How I learned to stop worrying and write my own ORM.

Por que o Dapper?

O Dapper surgiu para resolver um requisito de performance de um dos sites mais acessados do mundo. A maneira como ele foi construído e os resultados de performance o tornaram popular.

Com ele é possível exercitar as suas habilidades de SQL. Ele utiliza suas consultas e comandos da forma como você acha que deveria ser.

Os resultados de performance foram notáveis ao remover a complexidade da construção de queries, por causa das implementações da DSL.

O Dapper torna trivial a execução, parametrização e mapeamento de objetos através de consultas SQL.

Um Micro ORM

Enquanto um ORM contempla as features abaixo, o Dapper não, por isso é considerado um Micro ORM.

  • Gerar modelo de classes.
  • Gerar queries*.
  • Tracking das modificações do seu objeto para persistir no banco.
  • Não tem lazy loading.
  • Unit of Work api.
    * Existe uma biblioteca complementar, contrib, que pode gerar queries.

Tradicionalmente um full weight ORM contempla essas features.

Dapper vs SqlDataReader

Abaixo segue um típico exemplo utilizando o SqlDataReader.

Neste exemplo é necessário mapear item a item. Tratar nulos e default values. Tudo manualmente.

A seguir o mesmo exemplo, só que utilizando o Dapper.

Veja a simplicidade de utilização. Ganhos em economia de código, legibilidade com um custo de performance muito baixo. Um custo quase igual ao uso direto do SqlDataReader.

Resultado de performance

No Github do Dapper tem um teste de performance comparando às diferentes bibliotecas, inclusive o SqlDataReader.


Teste de performance efetuado pelo StackExchange

Uma diferença de 2ms para ter um código simples e de fácil manutenção.

Detalhes do Dapper

A implementação do Dapper consiste em oferecer Extension Methods que estendem o IDbConnection. Logo, pode rodar em qualquer banco de dados. Desde que implementado através do conjunto interfaces que compõem o IDbConnection.

Três estratégias resumem as funcionalidades do Dapper buscar, parametrizar a consulta e mapear o resultado.

Extensions Methods (Buscar)

O Dapper oferece sete Extension Methods para o IDbConnection, além de seus respectivos métodos assíncronos e dinâmicos. A seguir os detalhes de cada um deles.

Query

Executa uma query e mapeia o resultado da consulta para objeto c#. Deve obedecer a regra: Nome da Coluna SQL deve ter o mesmo nome na propriedade da classe.

Possivelmente o sucesso do dapper está diretamente ligado a este extension method. Este método será o mais utilizado por uma aplicação padrão.

Execute

Executa um comando no banco de dados e retorna a quantidade de linhas afetadas pelo comando. Útil para executar scripts de Insert, Update e Delete.


QueryFirst

Este método executa uma query e mapea a primeira linha do resultado. Se a consulta não tiver resultado, uma exceção InvalidOperationException é disparada.

Muito parecido ao Execute, com a diferença que mapeia todas as colunas. Pode ser utilizado para tabelas que devem ter registros, como tabelas de parametrização.

QueryFirstOrDefault

Executa uma query e mapeia a primeira linha do resultado. Se não houver resultado na consulta retorna null.


QuerySingle

A premissa do QuerySingle é que a consulta retorne uma única linha, caso contrário, uma exceção InvalidOperationException será disparada.


QuerySingleOrDefault

Solta uma exceção caso retorne mais de uma linha na consulta. Se não houver resultado, retorna null.


QueryMultiple

O QueryMultiple pode manipular múltiplos results sets, permitindo escrever consultas que retornem mais de um select. Para mais detalhes dele, leia este artigo: Dapper - .NET Core Tutorial - QueryMultiple

O QueryMultiple entrega um objeto do tipo GridReader. Este possui diversos métodos para Read.

A cada execução, o Read busca o Result Set no banco. Como um streaming, busca os results conforme a execução do código. O streaming acontece pois o QueryMultiple tem a opção buffered inativo.

Os métodos Read possuem variações como First, FirstOrDefault, Single entre outros. Os métodos e suas funcionalidades atuam com a mesma lógica dos Extensions Methods e seguindo o mesmo padrão de nomenclatura.

Parametrizando queries

Os extensions methods permitem parametrizar as queries de diversas maneiras.

Anonymous

Esta é a forma mais popular de parametrização, amplamente conhecida pela comunidade. Veja o exemplo.

A consulta recebe um parâmetro "@nomeDoParametro". A Extension Method recebe uma classe anônima com uma propriedade de mesmo nome. Os nomes devem ser iguais e são case insensitive.

Se for passado um array de objetos a mesma query é executada diversas vezes. Veja o exemplo

Foi enviado um array com três objetos. A query foi executada uma vez para cada um deles.

Dynamic

Ideal para executar Stored Procedures parametrizadas. Também para quem tem a necessidade de especificar o tipo do parâmetro. Veja o exemplo.

List

Chega de malabarismos para escrever queries que possuem o IN no WHERE. Veja como é simples a passagem de uma lista como parâmetro.

Dica Extra: Utilizando operador Like

Uma dica para utilizar o like de forma parametrizada, evitando ataques de SQL Injection.

Mapeando Objetos

Por fim o Dapper oferece diversas maneiras de mapear para Objetos os resultados das consultas. Completando assim seu ciclo.

Tipado

O método mais conhecido e utilizado. Segue o exemplo

O mapeamento é feito de acordo com as colunas do SQL Server e o Objeto. Caso o banco tenha nome de coluna que não corresponde ao objeto, um alias deve ser utilizado.


Dynamic

Se utilizar o Extension Method e nenhum objeto for oferecido, um tipo dynamic será retornado. Este formato pode ser especialmente útil em relatórios.


Cenários One to many

Há duas formas que podem ser utilizadas para mapear objetos filho com o Dapper.

Multi-Mapping

Em cenários onde é necessário não só mapear a classe desejada, como também as classes relacionadas, o Dapper oferece mecanismos.

Nestes cenários os mapeamentos começam a se tornar trabalhosos, difícil de manter. Veja este artigo do Renato Groffe que aborda com mais detalhes e exemplos que facilitam este cenário: Dapper: relacionamentos Um-para-Um e Um-para-Muitos (exemplos em ASP.NET Core)

Multiple result set

Essa estratégia pode ser seguido utilizando o QueryMultiple.

Mais detalhes deste cenário pode ser consultado em outro artigo que fiz:
Dapper - .NET Core Tutorial - QueryMultiple

Buffered vs Non-Buffered

Algumas extensions methods possuem a opção buffered. Que por padrão está ativo. Dessa forma todo resultado da query é lido de uma única vez. Se estiver inativo o resultado é em formato streaming e o conteúdo é carregado sob demanda. Útil em cenários onde trabalha com massa de dados, com isso melhora o consumo de memória. O QueryMultiple mantém ele inativo para as consultas.

Demo

Veja a aplicação construída para este artigo. Ela contém todas as queries e mais algumas.

Download

O código do projeto está disponivel no meu GitHub

Referências