Vocês conhecem o StackOverflow? Pois então, o maior fórum de perguntas/respostas para usuários/desenvolvedores de TI usa o Dapper como ORM para lidar com um banco de dados SQL Server. Performance é crucial para este fórum. A StackExchange é a empresa dona do StackOverflow e desenvolveu o Dapper para melhorar as performances das consultas no site, para que o processamento fosse mais eficiente e o retorno mais rápido. Dapper é uma biblioteca feita em .Net disponibilizada gratuitamente via NuGet, para que vocês possam adicionar aos seus projetos. É "open source" e está disponível no GitHub.
O Dapper estende a interface IDbConnection, que é a base para acesso a dados do ADO.NET. Lembrando que IDbConnection é implementada pelas classes SqlConnection, MySqlConnection, por exemplo. É por causa disso, que o Dapper pode ser utilizado para acessar tecnologias compatíveis com o .NET. Tais como SQL Server, MySQL, PostgreSQL, Oracle, entre outros.
Mas como isso começa?
Orientação a objetos é como as coisas são no C#, mas queremos recuperar dados em um banco de dados relacional. Inicialmente recuperávamos os dados vindo do banco e tínhamos que mapear manualmente esse resultado para nossos modelos de objetos. Daí surgiram os ORMs (um mapeador relacional para objeto) para facilitar a nossa vida, realizando automaticamente o mapeamento do resultado recuperado do banco com nossos modelos de objetos. Entre eles, talvez os mais famosos sejam o NHibernate (baseado no sucesso do Hibernate para Java) e o EF (Entity Framework), da Microsoft. Os ORMs implementaram vários recursos, além do simples fato de realizar o mapeamento automático. Dentre eles estão:
- Cuida de todos os detalhes técnicos das conexões e comunicações com o banco.
- Não precisa mais escrever instrução SQL. Vocês podem escrever uma instrução em C# e esta será convertida pelo ORM em instrução SQL.
- Como vocês escrevem em C#, então vocês não têm mais a "injeção de SQL".
- Vocês escrevem em C#, que é independente do sistema de armazenamento. Não importa se é MySQL, Oracle, SQL Server, entre outros.
- Obviamente por ser um Framework, aumenta a produtividade no desenvolvimento da aplicação.
Como nem tudo é perfeito e a tecnologia e inteligência por trás dos ORMs ainda está em evolução, há um custo no uso deles. O SQL gerado não é dos melhores e considerando outros fatores internos, as performances das consultas são muito ruins, principalmente em "joins". É nesse cenário que entra o Dapper como um "micro" ORM, com uma proposta de simplificar o trabalho junto ao ADO.NET através de seus objetos de conexão. Como comentei acima, com a implementação da interface IDbConnection. Ele é leve e de codificação simples. Só tem um detalhe. Vocês precisam voltar a escrever instruções SQL, mas ele está antenado com a segurança e oferece a parametrização para construir instruções SQL sem o risco da "injeção de SQL".
Eu coloquei "micro" entre aspas porque é como todos o chamam: "Micro ORM". Ele não possui 100% das funcionalidades do EF ou NHibernate, mas para mim ele não parece micro. Ele atende a todas as necessidades que já vivenciei e a algumas que ainda não utilizei. Ele possui recursos para trabalhar com “stored procedure”, tabelas, views, inclusões, alterações, exclusões e até transações. Muito importante: o Dapper está em ambiente de produção no StackOverflow e isso é uma garantia de que o mesmo sempre estará em evolução. Prova disso é que o mesmo já está em conformidade com o .Net Core.
Abaixo, uma tabela com um teste de performance feito envolvendo diversas tecnologias de acesso a dados. Vejam que o Dapper está em segundo lugar, atrás apenas do ADO.NET. E vejam o NHibernate e o EF. O resultado é espantoso.
Método | Duração |
---|---|
Hand coded (using a SqlDataReader) | 47 ms |
Dapper ExecuteMapperQuery | 49 ms |
ServiceStack.OrmLite(QueryByld) | 50 ms |
PetaPoco | 52 ms |
BLToolkit | 80 ms |
SubSonic CodingHorror | 107 ms |
NHibernet SQL | 104 ms |
Linq 2 SQL ExecuteQuery | 181 ms |
Entity Framework ExecuteStoreQuery | 631 ms |
Não fiquem tristes. Promessa feita, promessa cumprida. A equipe de desenvolvimento do EF Core fez com que ele tivesse uma performa muito próxima do Dapper e ADO.NET. Porém... E não podia faltar um "porém". Há ainda algumas questões técnicas envolvendo as consultas e sendo assim, se vocês têm necessidades reais de performances, devem usar o Dapper. Mas antes de seguir para o código... Gostaria de informar que eu não abandonei o EF. Eu o uso em muitas situações, principalmente para inclusão e alteração total do registro (todos os campos). Como o objetivo aqui é falar do Dapper, não apresentarei nada do EF.
Vou criar um projeto ASP.NET Web Application (.Net Framework) com o template do MVC sem autenticação. Eu trabalho no modelo "Code-First", então estou usando o Entity Framework para todo o processo de criação e manutenção da base. Sendo assim, toda a estrutura auxiliar para o programa rodar já está pronta.
Mas vamos lá. Tá na hora do código.
Para começar, temos duas entidades: Aluno e Estado. Onde um Aluno mora em um Estado e um Estado tem uma coleção de Alunos. No diagrama abaixo, represento Aluno se relacionando com Estado. Não me preocupei em colocar todas as propriedades das classes Aluno e Estado, porque todas as classes são detalhadas abaixo.
Apresentando a classe Aluno com todas as suas propriedades. Seguem alguns destaques:
- Linha 8, a propriedade chave da classe. Id: Guid. Seguida de um recurso do C# 6, o Auto-Property Initializers que inicializa a propriedade no momento de se instanciar o Aluno. Só será levado em conta no momento do Create.
- Linha 16 temos a propriedade FK (foreign key) EstadoId.
- Linha 17, temos a propriedade de navegação para a classe Estado.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3
4 namespace WebAppArtigoDepper.Models
5 {
6 public class Aluno
7 {
8 public Guid Id { get; set; } = Guid.NewGuid();
9 public string Nome { get; set; }
10 public string Mae { get; set; }
11 public string Apelido { get; set; }
12 public string Telefone { get; set; }
13 public string Email { get; set; }
14 public DateTime DataDeNascimento { get; set; }
15 public DateTime DthInclusao { get; set; }
16 public Guid EstadoId { get; set; }
17 public virtual Estado Estado { get; set; }
18 }
19 }
Apresentando a classe Estado com todas as suas propriedades. Seguem alguns destaques:
- Linha 8, a propriedade chave da classe. Id: Guid. Seguida de um recurso do C# 6, o Auto-Property Initializers que inicializa a propriedade no momento de se instanciar o Estado. Só será levado em conta no momento do Create.
- Linha 12, temos a propriedade de navegação para a classe Aluno. Usamos a propriedade ICollection<T> por causa do EF.
1 using System;
2 using System.Collections.Generic;
3
4 namespace WebAppArtigoDepper.Models
5 {
6 public class Estado
7 {
8 public Guid Id { get; set; } = Guid.NewGuid();
9 public string Nome { get; set; }
10 public string Sigla { get; set; }
11 public string Capital { get; set; }
12 public virtual ICollection Alunos { get; set; }
13 }
14 }
Criei uma classe Repository para o Dapper. Esta classe será herdada pelas classes Repossitory de cada entidade. Ela possui apenas a propriedade Connection do tipo IDbConnection, que recebe a string de conexão do banco configurada no WebConfig.
1 using System.Configuration;
2 using System.Data;
3 using System.Data.SqlClient;
4
5 namespace WebAppArtigoDepper.Models
6 {
7 public class DapperRepository
8 {
9 public IDbConnection Connection => new SqlConnection(ConfigurationManager
.ConnectionStrings["EntityContextDb"].ConnectionString);
11 }
12 }
A seguir apresentamos a classe AlunoRepository, onde teremos todos os métodos do CRUD. Eu dividi a classe com o recurso de partial class para facilitar a apresentação de cada método.
A classe AlunoRepository, apresentada a seguir, possui o método Obter para recuperar todos os registros. O método não possui parâmetros, e neste caso entendemos que retornará uma lista de Alunos. Essa é a instrução mais simples, pois trata-se de uma simples string de SQL para consulta. Seguem alguns destaques:
- Linha 6, implementada a herança da classe DapperRepository.
- Linha 10, início da instrução using que é usada para instanciar um objeto "cn" utilizando a propriedade herdade "Connection". O Dapper implementa o padrão Dispose Pattern, e por isso usamos a instrução using.
- Linha 12, uma simples string de consulta SQL.
- Linha 14, abrimos a conexão com o banco.
- Linha 15, chamamos o método Query com o parâmetro de tipo "Aluno". Este método possui sobrecargas. Nesta, só precisamos passar a variável com o SQL. Este método retornará a lista de Alunos.
- Uma instrução using para instanciar o objeto de conexão.
- Definição de uma variável com o SQL.
- Abertura da conexão.
- Execução do método Query.
1 using Dapper;
2 using System.Collections.Generic;
3
4 namespace WebAppArtigoDepper.Models
5 {
6 public partial class AlunoRepository : DapperRepository
7 {
8 public IEnumerableObter()
9 {
10 using (var cn = Connection)
11 {
12 var sql = @"SELECT Id, Nome, Mae, Apelido, Telefone, Email, DataDeNascimento, DthInclusao, EstadoId
FROM Alunos ORDER BY Nome";
13
14 cn.Open();
15 return cn.Query(sql);
16 }
17 }
18 }
19 }
Vamos falar do método Obter para recuperar o registro de um Aluno. Neste caso, o método recebe o parâmetro id que identifica o registro desejado e que será passado para o método de consulta do Dapper.
- Linha 12, o SQL agora possui um parâmetro @ID. Essa sintaxe impede a "injeção de sql".
- Linha 15, agora utilizamos o método QueryFirstOrDefault do objeto de conexão. Este método retorna um objeto, ou nulo caso não haja resultado para a consulta. Este método possui sobrecargas.
- Linha 15. o método QueryFirstOrDefault é apresentado com a passagem do segundo parâmetro: um objeto anônimo onde temos a propriedade ID, que é o parâmetro @ID da consulta SQL.
- O método Query, apresentado no código anterior, também possui o parâmetro do objeto anônimo, com o mesmo objetivo de passar valores para o SQL, protegendo contra a injeção de SQL.
1 using Dapper;
2 using System;
3
4 namespace WebAppArtigoDepper.Models
5 {
6 public partial class AlunoRepository
7 {
8 public Aluno Obter(Guid id)
9 {
10 using (var cn = Connection)
11 {
12 var sql = @"SELECT Id, Nome, Mae, Apelido, Telefone, Email, DataDeNascimento, DthInclusao, EstadoId
FROM Alunos WHERE Id = @ID";
13
14 cn.Open();
15 return cn.QueryFirstOrDefault(sql, new { ID = id });
16 }
17 }
18 }
19 }
Vamos agora de método Adicionar. Ele recebe como parâmetro a entidade Aluno. Lembrando que a proposta é apresentar o Dapper. Em uma situação normal, eu uso o EF para realizar inclusões.
- Linha 11, temos o nosso SQL com um parâmetro para cada campo.
- Linha 14, agora temos o método Execute do Dapper, que retorna 1, caso a adição tenha acontecido com sucesso. O retorno representa a quantidade de registros afetados pela instrução.
- Linha 14, se você enviar uma instrução SLQ com dois registros para ser incluído, o método Execute retornará 2 em caso de sucesso das inclusões.
- Linhas 16-24, repare que não há um código tipo Id = entidade.Id, Nome = entidade.Nome e assim por diante. Se a propriedade de um objeto anônimo, tem o mesmo nome da propriedade de um outro objeto que está sendo utilizada na atribuição, então não precisamos dar nome a propriedade em questão. Legal! Não é?
1 using Dapper;
2
3 namespace WebAppArtigoDepper.Models
4 {
5 public partial class AlunoRepository
6 {
7 public int Adicionar(Aluno entidade)
8 {
9 using (var cn = Connection)
10 {
11 var sql = @"INSERT INTO Alunos
(Id, Nome, Mae, Apelido, Telefone, Email,
DataDeNascimento, DthInclusao, EstadoId )
VALUES (@Id, @Nome, @Mae, @Apelido, @Telefone, @Email,
@DataDeNascimento, @DthInclusao, @EstadoId )";
12
13 cn.Open();
14 return cn.Execute(sql, new
15 {
16 entidade.Id,
17 entidade.Nome,
18 entidade.Mae,
19 entidade.Apelido,
20 entidade.Telefone,
21 entidade.Email,
22 entidade.DataDeNascimento,
23 entidade.DthInclusao,
24 entidade.EstadoId
25 });
26 }
27 }
28 }
29 }
No método Alterar temos basicamente a mesma situação do método Adicionar.
1 using Dapper;
2
3 namespace WebAppArtigoDepper.Models
4 {
5 public partial class AlunoRepository
6 {
7 public int Alterar(Aluno entidade)
8 {
9 using (var cn = Connection)
10 {
11 var sql = @"UPDATE Alunos
SET Nome = @Nome
, Mae = @Mae
, Apelido = @Apelido
, Telefone = @Telefone
, Email = @Email
, DataDeNascimento = @DataDeNascimento
, DthInclusao = @DthInclusao
, EstadoId = @EstadoId
WHERE Id = @Id";
12
13 cn.Open();
14 return cn.Execute(sql, new
15 {
16 entidade.Id,
17 entidade.Nome,
18 entidade.Mae,
19 entidade.Apelido,
20 entidade.Telefone,
21 entidade.Email,
22 entidade.DataDeNascimento,
23 entidade.DthInclusao,
24 entidade.EstadoId
25 });
26 }
27 }
28 }
29 }
E no método Remover, mais uma vez, temos basicamente a mesma situação do método Adicionar.
1 using Dapper;
2 using System;
3
4 namespace WebAppArtigoDepper.Models
5 {
6 public partial class AlunoRepository
7 {
8 public int Remover(Guid id)
9 {
10 using (var cn = Connection)
11 {
12 var sql = @"DELETE FROM Alunos WHERE Id = @Id";
13
14 cn.Open();
15 return cn.Execute(sql, new { Id = id });
16 }
17 }
18 }
19 }
Segue abaixo algumas coisas que fiz ou falei neste artigo, mas não expliquei por não fazer parte do escopo do mesmo. Poderão ser explicadas em outros artigos e quando isso acontecer, voltarei aqui e adicionarei os links correspondentes.
- Code-first e Entity Framework.
- Id, nome da propriedade chave da classe.
- Guid, tipo da propriedade chave da classe.
- Virtual, palavra-chave usada nas propriedades de navegação, por causa do EF.
- Partial Class, divisão do conteúdo de uma classe em vários arquivos.
No momento, ainda não habilitei os comentários nas publicações. Porém, todas as publicações são compartilhadas em minhas redes sociais. Você pode comentar em uma delas. Aliás, é provável que tenha chegado até aqui por elas, então volta lá e deixe seu comentário, que eu respondo.
Por hora, é isso aí pessoal. No próximo artigo da Série Dapper, vou falar sobre as outras instruções SQL bem legais.
Quer saber por onde eu ando? Eu ando sempre por aqui.