Dapper Unit of Work e Repository Pattern

O Dapper é um Micro ORM muito poderoso e que nos dá muita liberdade, porém com grandes poderes trazem grandes responsabilidades e neste artigo vamos conferir como implementar alguns padrões com ele.

Dapper

O Dapper é um Micro ORM mantido pelo StackOverflow com o foco em performance. Ele se difere de outros ORM por ser mais simples em seu contexto, que é o motivo pelo qual ele consegue performar melhor.

O Dapper na verdade só extende o IDbConnection simplificando a leitura e escrita no banco. Por baixo dos panos ele roda sobre o contexto de acesso a dados do .NET.

A principal diferença entre ele e o Entity Framework (ORM) por exemplo é que o EF é mais completo, traz bem mais facilidades, mas obviamente estas facilidades trazem um custo maior de performance.

Embora haja como otimizar bastante as consultas utilizando o EF, em alguns pontos eu prefiro utilizar o Dapper, ou mesmo mesclar os dois, usando Dapper para leitura e o EF para escrita.

Para utilizar o Dapper em seu projeto basta instalar o pacote abaixo.

dotnet add package Dapper

Conexão e Transação

A primeira grande responsabilidade quando trabalhamos somente com Dapper é gerenciar a conexão com o banco de dados. Enquanto ao utilizar o EF temos um DataContext, aqui é um trabalho mais artesanal.

Para este exemplo, vamos utilizar a classe abaixo e se você ficar curioso sobre o motivo de ela ser selada, confira este artigo.

public sealed class DbSession : IDisposable
{
    private Guid _id;
    public IDbConnection Connection { get; }
    public IDbTransaction Transaction { get; set; }

    public DbSession()
    {
        _id = Guid.NewGuid();
        Connection = new SqlConnection(Settings.ConnectionString);
        Connection.Open();
    }

    public void Dispose() => Connection?.Dispose();
}

Nesta classe temos uma conexão e uma transação, definidas pelo IDbConnection e IDbTransaction respectivamente. A escolha para estes tipos ao invés do ISqlConnection e ISqlTransaction foi a necessidade do pacote adicional System.Data.SqlClient necessário para se utilizar estes dois últimos tipos mencionados.

Adicionei também um _id na sessão, apenas para acompanhar os testes, pode removê-lo se quiser pois ele não tem efeito algum sobre o resto do código.

O IDbConnection será responsável por gerenciar a conexão com o banco de dados enquanto o IDbTransaction será responsável pelas transações, mas vamos deixar as transações para depois.

Temos o método construtor onde SEMPRE iniciamos uma nova conexão com o banco de dados através do método Connection.Open().

Para finalizar, temos o método Dispose vindo da interface IDisposable que será executado no término da instância desta classe.

Implementar o IDisposable nos permite fazer uso desta classe em contextos utilizando o using do C#, que já garante o término da mesma no fim da excução do contexto.

Por fim no método Dispose fazemos o fechamento da conexão chamando o Connection?.Dispose(). Note que temos um ? que faz a checagem caso o objeto Connection seja null.

Close ou Dispose?

O objeto Connection tem dois métodos similares, o Close e o Dispose. Ambos fecham a conexão com o banco de dados, porém o Close ainda mantém o objeto Connection ativo.

Em suma, se você pretende reabrir esta mesma conexão futuramente, a melhor escolha é utilizar o Close, caso contrário, utilizando o Dispose precisará gerar um novo objeto Connection.

Unit of Work

O padrão unidade de trabalho (Unit of Work) prevê a centralização das transações e em alguns casos até dos repositórios em um unico ponto.

O objetivo principal é garantir a transação entre todos os repositórios de forma uniforme. Em suma ele é um objeto bem simples, que só comita ou desfaz tudo que foi gerado na transação.

Novamente, como estamos trabalhando com objetos de conexão/transação no banco de dados, é importante implementar o IDisposable para garantir que nenhuma conexão ficará aberta.

public interface IUnitOfWork : IDisposable
{
    void BeginTransaction();
    void Commit();
    void Rollback();
}

Na interface, declaramos apenas três métodos, para iniciar a transação (BeginTransaction), para comitar as mudanças (Commit) e para desfazer tudo caso algo dê errado (Rollback).

A implementação desta interface só precisa de uma instância do DbSession, com ela podemos criar uma nova transação e realizar o commit ou rollback da mesma.

public sealed class UnitOfWork : IUnitOfWork
{
    private readonly DbSession _session;

    public UnitOfWork(DbSession session)
    {
        _session = session;
    }

    public void BeginTransaction()
    {
        _session.Transaction = _session.Connection.BeginTransaction();
    }

    public void Commit()
    {
        _session.Transaction.Commit();
        Dispose();
    }

    public void Rollback()
    {
        _session.Transaction.Rollback();
        Dispose();
    }

    public void Dispose() => _session.Transaction?.Dispose();
}

Repository Pattern

O padrão repositório (Repository) prevê um ponto único de acesso a dados para uma entidade e frameworks como EF Core já trazem uma implementação do mesmo.

Os repositórios em suma não são o maior desafio, mas sim suas transações, principalmente entre repositórios.

Para este exemplo, separei o repositório de notificações com dois métodos simples de leitura e escrita.

public class NotificationRepository
{
    private DbSession _session;

    public NotificationRepository(DbSession session)
    {
        _session = session;
    }

    public IEnumerable<NotificationModel> Get()
    {
        return _session.Connection.Query<NotificationModel>("SELECT * FROM [Notifications]", null, _session.Transaction);
    }

    public void Save(NotificationModel model)
    {
        _session.Connection.Execute("INSERT INTO [Notifications] VALUES(NEWID(), 'Title', 'URL', GETDATE())", null, _session.Transaction);
    }
}

Note que no final dos métodos nós temos o _session.Transaction sendo informado. Isto fará com que o Dapper considere a transação atual caso ela exista.

Sendo este um contexto transacionado, o método Save que realiza um INSERT só será considerado quando chamarmos o método Commit do nosso UnitOfWork.

Injeção de Dependência

Para prosseguirmos, precisamos primeiro resolver as dependências que criamos e o ASP.NET faz bem isto através da sua DI nativa.

services.AddScoped<DbSession>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddTransient<NotificationRepository>();

Note que utilizamos AddScopped para o DbSession e AddTransient para o UnitOfWork, caso esteja confuso para você esta parte, recomendo a leitura deste artigo.

Controllers

Agora que temos tudo pronto, podemos finalizar nossa API simplesmente fazendo as devidas chamadas no controlador.

Chamadas não transacionadas

A primeira chamada, apenas um GET, não será transacionada, então não precisamos do UnitOfWork aqui.

[HttpGet("")]
public IEnumerable<NotificationModel> Get(
    [FromServices] NotificationRepository repository)
{
    return repository.Get();
}

Chamadas transacionadas

Nossa segunda chamada será uma inserção, e queremos ter a possibilidade de revertê-la caso algo dê errado. Indo ainda mais afundo, queremos revertê-la caso algo dê errado em outro repositório inclusive.

Desta forma precisamos de uma instância do UnitOfWork e chamar o método BeginTransaction antes das chamadas do repositório.

Isto criará uma transação a nível do controlador e todas as chamadas ao banco informando _session.Transaction ficarão neste escopo.

Como resultado temos a possibilidade de persistir (Commit) ou reverter (Rollback) tudo que foi realizado nesta transação com a nossa unidade de trabalho (UnitOfWork).

[HttpPost("")]
public IActionResult Post(
    [FromServices] IUnitOfWork unitOfWork,
    [FromServices] NotificationRepository repository)
{
    unitOfWork.BeginTransaction();
    repository.Save(new NotificationModel
    {
        Date = DateTime.Now,
        Id = Guid.NewGuid(),
        Title = "Teste",
        Url = "Teste"
    });
    unitOfWork.Commit();
    return Ok();
}

Código Fonte

Conclusão

Embora frameworks ORM como EF Core nos tragam vários benefícios, em diversos momentos podemos nos deparar com a necessidade de um trabalho mais artesanal no acesso à dados e isto pode trazer algumas dificuldades extras como transação e conexões ao banco.

Com UnitOfWork, Dapper e Repository Pattern conseguimos entregar algo consistente e performático levando em conta os cenários acima.

Populares

Priority Queue

Priority Queue ou fila prioritária é um tipo de lista inclusa no C# 10 que permite que seus itens...


Implicit Operators no C#

Implicit Operators permitem adicionar comportamentos de conversão a objetos Built In ou complexos...


ASP.NET 5 – Autenticação e Autorização com Bearer e JWT

Este artigo atualmente utiliza a versão 5.0.0-rc.1 do ASP.NET/.NET, o que significa que ainda não...


Clean Code - Guia e Exemplos

Saiba como manter seu código limpo (Clean Code) seguindo algumas práticas sugeridas pelo Robert C...


Git e GitHub - Instalação, Configuração e Primeiros Passos

Git é um sistema de controle de versões distribuídas, enquanto GitHub é uma plataforma que tem o ...


Compartilhe este artigo



Conheça o autor

Me dedico ao desenvolvimento de software desde 2003, sendo minha maior especialidade o Desenvolvimento Web. Durante esta jornada pude trabalhar presencialmente aqui no Brasil e Estados Unidos, atender remotamente times da ?ndia, Inglaterra e Holanda, receber 8x Microsoft MVP e realizar diversas consultorias em empresas e projetos de todos os tamanhos.





2.651

Aulas disponíveis

249

horas de conteúdo

64.479

Alunos matriculados

45.609

Certificados emitidos





Comece de graça agora mesmo!

Temos mais de 19 cursos totalmente de graça e todos com certificado de conclusão.

Começar


Prefere algo mais Premium?

Conheça nossos planos



Premium semestral

Compra única, parcelada em até
12x no cartão de crédito


12x R$

49

,78

=R$ 597,36
  • 6 meses de acesso
  • Acesso à todo conteúdo
  • Emissão de Certificado
  • Tira Dúvidas Online
  • 59 cursos disponíveis
  • 10 carreiras disponíveis
  • 161 temas de tecnologia
  • Conteúdo novo todo mês
  • Encontros Premium

Começar agora

Política de privacidade

Premium anual

Compra única, parcelada em até
12x no cartão de crédito


12x R$

84

,78

=R$ 1.017,36
  • 1 ano de acesso
  • Acesso à todo conteúdo
  • Emissão de Certificado
  • Tira Dúvidas Online
  • 59 cursos disponíveis
  • 10 carreiras disponíveis
  • 161 temas de tecnologia
  • Conteúdo novo todo mês
  • Encontros Premium

Começar agora

Política de privacidade



Precisa de ajuda?

Dúvidas frequentes



  • Posso começar de graça?

    Sim! Basta criar sua conta gratuita no balta.io e começar seus estudos. Nós contamos com diversos cursos TOTALMENTE gratuitos e com certificado de conclusão.

  • Vou ter que pagar algo?

    Nós temos cursos gratuitos e pagos, porém você não precisa informar nenhum dado de pagamento para começar seus estudos gratuitamente conosco. Os cursos gratuitos são completos e com certificado de conclusão, você não paga nada por eles.

    Porém, caso queira algo mais Premium , você terá acesso à diversos benefícios que vão te ajudar ainda mais em sua carreira.

  • Por onde devo começar?

    Siga SEMPRE as nossas Carreiras , elas vão te orientar em todos os sentidos. Os cursos já estão organizados em categorias e carreiras para facilitar seu aprendizado.
    Nossa sugestão para aprendizado é começar pelo Backend e seguindo para Frontend e Mobile.

    • Backend
    • Frontend
    • Mobile

  • Os cursos ensinam tudo que preciso?

    Nenhum curso no mundo vai te ensinar tudo, desculpa ser sincero! Os cursos são uma base, eles fornecem por volta de 30% do que você precisa aprender, o resto é com você, com dedicação e MUITA prática.

  • O que eu devo estudar?

    Java ou .NET? Angular ou React? Xamarin ou Flutter? A resposta é simples e direta: "Você já sabe o básico?"

    Se você ainda não sabe BEM o básico, ou seja, os fundamentos, OOP, SOLID, Clean Code, está perdendo tempo estudando Frameworks ou até coisas mais avançadas como Docker. Foque nos seus objetivos primeiro.
    Agora se você está indeciso sobre qual Framework estudar, a boa notícia é que o mercado neste momento está bem aquecido e você tem várias oportunidade. Desta forma o que levaríamos em conta para tomar esta decisão seria:

    • Já sei o básico
    • O Framework/Tecnologia tem mercado onde eu estou (região)
    • O Framework/Tecnologia é utilizado em uma empresa onde quero atual
    • O Framework/Tecnologia resolve meu problema
    • Eu gosto de utilizar o Framework/Tecnologia

  • Estou pronto para estudar no balta.io?

    Com certeza! O primeiro passo é começar e você pode fazer isto agora mesmo!

    Começar de graça

Ainda tem dúvidas?





Assine nosso Newsletter

Receba em primeira mão todas as nossas novidades.

Cadastrar


balta.io