C#: Usando Dapper com FluentMap, Linq e Lambda para consultas

Filipe Ceccon de Alencar
filipececcon

--

Não é nenhuma novidade usar um ORM para acessar um banco de dados, mas se na sua empresa e/ou projeto que está trabalhando ainda é usado ADO.NET, ou se você precisa de uma implementação bem simples mas poderosa em questão de produtividade e funcionalidade, ou até mesmo dar uma boa organizada/refatorada naquele projeto já obsoleto, aqui vai uma boa dica.

Para quem já usou o Entity Framework ou NHibernate sabe que ORMs poderosos com grandes funcionalidades e implementações, mas às vezes ficamos podados em usá-los por conta de vários fatores.

Se o banco não está muito bem modelado, ou o cenário está maçante de trabalhar com a camada de persistência, contém aquelas milhões de procedures e functions, onde não tem repositório (Repository Pattern), quiçá uma camada de acesso à dados, às vezes usar um ORM mais parrudo pode não ser a melhor saída, talvez onere mais ainda as consultas com o banco e pode deixar a aplicação mais lenta.

O Dapper também não é novidade, teve sua primeira versão em 2011 disponível para download no Nuget. Para você programador vai uma informação legal. Sabe aquela nossa bíblia chamada Stackoverflow? É feito com Dapper.

Entretanto em contato com alguns programadores o mencionei, e não o conheciam, e os que conheciam, não sabiam que era possível essa implementação com Linq e Lambda e por isso resolvi escrever esse artigo, pois sei que existem vários cenários que podemos melhorar.

Nos blogs de hoje em dia se postam várias novidades e se esquecem/não se importam com os casos já começados e que precisam de uma revigorada.

Sei que é inevitável, mas não podemos comparar o uso dessa abordagem com o Entity Framework ou NHibernate, seria até desonesto, mas podemos comparar com o ADO. O que quero mostrar é um modo bem simples e muito produtivo de se trabalhar com o micro-orm e que pode poupar bastante código. Existem milhões de abordagens melhores do que essa, principalmente na gringa, mas acho válido fazer esse artigo. Espero que gostem!

Atenção toda essa abordagem funciona até a versão 4.5.1 do .NET após isso não irá funcionar por conta de restrição incompatibilidade dos pacotes instalados.

Todo o projeto está emmeu github com toda essa implementação já feita e bem comentada para a apreciação de vocês.

Para você que baixou o projeto restaure os pacote no Nuget, coloque sua connection string e pode rodar normalmente.

Você que usa ADO, gostaria de reduzir seu código na camada de dados e usar Linq e expressões Lambda para fazer consultas no banco? Vamos lá…

Escopo Inicial

  1. Antes de começar o nosso projeto se conecte a um banco SQL SERVER e rode o script abaixo:

Seu banco deve ficar assim:

2. Agora sim! Vamos criar uma solution em branco no Visual Studio e nomeie como quiser (no meu caso: DapperWithLambdaAndLinqExample)

3. Explore o local da solution e crie a pasta “src”, é lá que colocaremos nossos projetos

src

4. Criaremos, 3 projetos na solution, mas dentro da pasta “src”, sendo deles

  • Domain como Class Library
  • Repository como Class Library
  • Tests como Unit Test Project

Obs: Todos com o framework 4.5

Deve ficar dessa forma:

sln

5. Agora adicionamos as referências:

  • No Repository adicione o Domain e o assembly System.Configuration
  • No Tests adicione Domain e Repository

6. Criaremos uma modelagem simples (obviamente só para exemplificar o uso da ferramenta) dentro da Domain. Crie duas pastas dentro dela: uma chamada Contracts e outra Entities

7. Dentro da pasta Entities crie uma classe com o nome BaseEntity conforme o código abaixo:

8. Vamos criar a classe Player conforme o código abaixo:

9. Vamos criar a classe Team conforme o código abaixo:

10. Dentro da pasta Contracts agora vamos criar uma interface com o nome IRepositoryBase conforme código abaixo:

Nossa estrutura de pastas e classes da camada Domain ficou assim:

domain

Camada Repository

1. Começaremos instalando os pacotes que serão necessários para implementação sendo eles:

  • Dapper (v1.50.0-rc2b)
  • FluentMap (v1.5.0)
  • Dommel (v1.6.0)
  • FluentMap.Dommel (v1.4.0)

Obs: os pacotes necessitam dessa versão ou maior do Dapper para rodar, apesar de ser pre-release, já está bem consolidada por sinal e funciona muito bem.

Instale o Dapper primeiro e pelo Package Manager Console com a linha:

Install-Package Dapper -Pre

Depois pode instalar pela interface do Nuget se preferir. Ficará assim:

pkgs

2. Vamos criar duas pastas: uma chamada Mappers e a outra Repositories

3. Na raiz da camada Repository criaremos a classe RepositoryBase. Ela que nos servirá de uma implementação base, será o nosso padrão, é dela que iremos herdar todos nossos repositórios. Qualquer implementação personalizada nas classes filha, tem de ser sobrescrita com o cláusula override no método. Segue conforme código abaixo:

4. Agora dentro da pasta Repositories criaremos duas classes: uma PlayerRepository e a outra TeamRepository, conforme o código abaixo:

Estamos quase lá! Precisamos agora mostrar para o Dommel quem são nossas entidades e as tabelas que representam e é agora que começa a mágica!

5. Agora dentro da pasta Mappers iremos criar nosso primeiro mapeamento chamado PlayerMap conforme o código abaixo:

6. Dentro da mesma pasta criaremos o mapeamento TeamMap conforme código abaixo:

Temos de herdar da classe DommelEntityMap assim os métodos da classe RepositoryBase reconhecerão os mapeamentos, mas ainda precisamos criar uma classe que os inicialize, que os registre-os no start da nossa aplicação.

7. Na raiz da camada Repository criaremos a classe RegisterMappings conforme o código abaixo:

Esse método Register deve ficar sempre no start da sua aplicação. Por exemplo, se você tem um projeto ASP.NET MVC/WebAPI, deve ficar no arquivo Global.asax no método Application_Start, se tiver um projeto Windows Forms (por padrão) deve ficar no arquivo Program.cs no método Main antes de chamar o primeiro formulário e etc.

Essa classe FluentMapper que é responsável por adicionar e inicializar os mapeamentos. Note a última linha de configuração, config.ForDommel é essa a linha responsável para os métodos do Dommel reconhecerem os mapeamentos e tudo acontecer. Essa linha é extremamente importante, sem ela o repositório padrão não funcionará.

Dessa forma nossa camada Repository fica assim:

repo

Camada Tests

1. Vamos instalar um pacote que é necessário para o uso do Dapper/Dommel na camada de testes, se não for instalado dará um erro que a dll System.Reflection.TypeExtensions não pode ser encontrada. Então instale a dll pelo Nuget (v4.1.0)

2. Agora vamos criar nossa classe de testes para ver como nossa implementação do repositório PlayerRepository funciona, conforme o código abaixo:

Optei por criar em um projetos de testes pois assim pode-se rodar os métodos separadamente. Note que no método construtor da classe eu chamo o RegisterMapping.Register para registrar nossos mapeamentos antes de rodar qualquer teste. (Poderíamos também ter usado um método qualquer com o atributo [TesteInitialze], também daria o mesmo efeito)

3. Por último vamos adicionar no config a nossa connection string, se você ainda não sabe fazer isso, pode pesquisar melhor sobre isso na internet que existe várias maneira de chegar até ela. Colocarei abaixo um exemplo de escopo.

<connectionStrings>
<add name="MyDatabase" connectionString="Minha_string_de_conexao" />
</connectionStrings>

Pronto! A essa altura seu teste deve funcionar. Você pode colocar breakpoints neles e os rodar como Debug assim conseguirá ver passo a passo da implementação.

O bom disso é que agora você não precisa ficar adicionando e tipando parâmetros sql a cada execução no repositório e/ou não será necessária uma implementação complexa com Reflection para fazer de forma generalista. Apenas com isso já dá pra fazer um muita coisa e a performance não fica ruim. Segundo o próprio github do Dapper a perca de performance comparada a uma implementação com ADO mínima.

Vamos nos focar para o método GetAll no PlayerTest. Esse método é do Dommel, para o mapeamento funcionar você precisa utilizar os métodos que consulta dele, sem isso o mapeamento não será feito e seu retorno será o objeto nulo.

Ao buscar a lista note que o time dentro de cada jogador está como null.

lazy

Como eu disse não podemos comparar com uma função de LazyLoad do Entity Framework. O Dapper não vai fazer isso para você. Nesse caso temos de sobrescrever (override) o método padrão. Se você pensou em aproveitar o TeamRepository e fazer um GetById em cada id de time retornado para injetar no objeto, pensou errado!

Dessa forma iríamos uma vez no banco para pegar os jogadores e depois mais uma vez para cada time de cada jogador, isso iria onerar muito o banco. A melhor abordagem é fazer um INNER JOIN em uma única query. O Dapper nos oferece a opção de fazer JOIN, também de forma simples, e o Dommel nos facilita com o método dele encapsulando ainda mais o Dapper. Vá até o arquivo PlayerRepository e cole esse trecho de código abaixo:

Note que:

  • Sobrescrevemos a implementação padrão.
  • Na instrução GetAll passamos <classePai, subClasse, classeDeSaida>
  • Configuramos a injeção de valores no bloco de delegate onde a variável team que respresenta o Time, e atribuímos para a subclasse dentro de Jogador
  • Retornamos o jogador já com a referência correta

Se você suspeita de que ele não fosse o JOIN pra você no banco e sim na aplicação, eu vou sanar sua dúvida. Fui até o banco de dados e dei um trace para ver o que estava rolando por lá e peguei a instrução exata do JOIN.

Também podemos fazer uma consulta com a cláusula WHERE e o melhor de tudo usando expressão lambda para isso. Se você foi observador, note que na classe RepositoryBase tem um método GetList que está esperando um predicado, uma expressão lambda. Em cima dessa expressão ele vai montar um WHERE na consulta de acordo com o seu mapeamento. Se você explorar o método Select do Dommel verá que no final existe uma classe chamada SqlExpression que irá ler o mapeamento do seu lambda e irá fazer o parse para uma clausula WHERE.

where

Essa é sem dúvida uma das funcionalidades mais legais dessa implementação. Todavia, como já falado anteriormente, não podemos compará-lo com outros ORMs, nesse caso ele olha somente para a classe principal. Se você passar sua expressão lambda usando uma subclasse, não irá funcionar. Infelizmente se você que quiser usar o lambda retornar os times populados dentro dos jogadores terá de usar uma interação (método Select do Linq) e buscar usando um GetById para cada um. Tentei de várias formas e não achei nenhuma abordagem clean para repassar. O bom é que mesmo usando ele, podemos utilizar a forma padrão do Dapper para fazer o INNER JOIN e usando somente o Dapper sem o Dommel ficaria como o código abaixo:

Que por sinal, se compararmos com ADO fica muito mais limpo o nosso código.

Existem formas ainda muito mais trabalhadas do que essa, abordagens muito bem elaboradas e com toda certeza usando Reflection. Essa é uma abordagem, existem infinitas formas de fazer isso acontecer. Se você tem a intenção de fazer um repositório genérico unificado, aqui vai um bom link para um Insert genérico usando Dapper [clique aqui].

Espero que tenham gostado e que seja útil para alguém.

--

--

Filipe Ceccon de Alencar
filipececcon

Metaleiro, pai de família, programador e entusiasta do empreendedorismo