ASP.Net Core - Padrão repositório e Unit of Work com Dapper


Hoje temos um tutorial mostrando como implementar o Padrão Repositório e o Unit of Work usando o Dapper no ambiente .NET Core.

Para saber mais sobre o Dapper, o padrão Repositório e UnitOfWork  consulte as referências no final do artigo.


O objetivo é mostrar como usar o Dapper para implementar o padrão Repository e o padrão Unit Of Work em uma Web API ASP.NET Core acessando o SQL Server.

Vamos iniciar criando o banco de dados TarefasDB e a seguir a tabela Tarefas :

USE TarefasDB
GO
CREATE TABLE [dbo].[Tarefas](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Nome] [nvarchar](100) NOT NULL,
    [Descricao] [nvarchar](50) NOT NULL,
    [Status] [int] NOT NULL,
    [ConcluidaEm] [datetime] NOT NULL,
    [CriadaEm] [datetime] NOT NULL,
    [ModificadaEm] [datetime] NULL
)

Vamos gerenciar as informações sobre tarefas em uma API Rest.

Recursos usados:

Criando a Web API no VS 2019

Abra o VS 2019 Community e selecione : Create a new Project;

Selecione o template ASP.NET Core Web API;

Informe o nome do projeto - ApiTarefas e sua localização;

A seguir marque as opções conforme a figura a seguir:

Com o projeto criado vamos incluir as referências ao Dapper e ao System.Data.SqlClient no projeto.

Podemos fazer isso via menu Tools->..-> Manage Nuget Packages for Solution ou usando o comando Install-package <nome-pacote>

A seguir vamos criar uma pasta Models no projeto e nesta pasta a classe:  Tarefa e a enumeração StatusTarefa que representam o nosso modelo de domínio:

1- Tarefa

public class Tarefa
{
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Descricao { get; set; }
        public StatusTarefa Status { get; set; }
        public DateTime ConcluindaEm { get; set; }
        public DateTime CriadaEm { get; set; }
        public DateTime? ModificadaEm { get; set; }
}

1- StatusTarefa

public enum StatusTarefa
{
      Criada,
      Ativa,
      Concluida,
      Cancelada
}

Estamos usando uma classe POCO anêmica sem muita expressão porque estamos em um tutorial demo.

Definindo a sessão com o banco de dados e a string de conexão

Crie uma pasta Data no projeto e nesta pasta vamos criar a classe DBSession que vai obter a string de conexão , abrir e gerenciar a conexão com o banco de dados SQL Server:

using Microsoft.Extensions.Configuration;
using System.Data;
using System;
using System.Data.SqlClient;

namespace ApiTarefas.Data
{
    public sealed class DBSession : IDisposable
    {
        public IDbConnection Connection { get; }
        public IDbTransaction Transaction { get; set; }
        public IConfiguration _configuration { get; }

        public DBSession(IConfiguration configuration)
        {
            _configuration = configuration;
            var conexaoSQLServer = configuration.GetConnectionString("
DefaultConnection");
            Connection =
new SqlConnection(conexaoSQLServer);
            Connection.Open();
        }

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

Note que esta classe é marcada com o modificador sealed e dessa forma não poderá ser estendida. Fizemos isso para proteger o código pois ele é o coração da nossa implementação. Ela também implementa IDisposable de forma que poderemos sempre fechar a conexão e liberar os recursos usados.

Agora abra o arquivo appsettings.json e inclua a seção ConnectionStrings contendo a string de conexão com o banco de dados SQL Server:

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=;Initial Catalog=TarefasDB;Integrated Security=True"
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Implementando o padrão Repository

Para implementar o padrão Repository vamos criar uma pasta Repositories no projeto e a seguir criar as interfaces :

  1. IGenericRepository<T>
  2. ITarefaRepository<T>

Vamos implementar um repositório genérico.

1- IGenericRepository

using System.Collections.Generic;
using System.Threading.Tasks;

namespace ApiTarefas.Repositories
{
    public interface IGenericRepository<T> where T : class
    {
        Task<T> Get(int id);
        Task<IEnumerable<T>> GetAll();
        Task<int> Add(T entity);
        Task<int> Delete(int id);
        Task<int> Update(T entity);
    }
}

2- ITarefaRepository

using ApiTarefas.Models;

namespace ApiTarefas.Repositories
{
    public interface ITarefaRepository : IGenericRepository<Tarefa>
     {
     }
}

Agora na mesma pasta vamos implementar a interface do repositório criando a classe concreta TarefaRepository:

3- TarefaRepository

using ApiTarefas.Data;
using ApiTarefas.Models;
using Dapper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ApiTarefas.Repositories
{
    public class TarefaRepository : ITarefaRepository
    {
        private DBSession _session;

        public TarefaRepository(DBSession db)
        {
            _session = db;
        }


        public async Task<int> Add(Tarefa entity)
        {

            entity.CriadaEm = DateTime.Now;
            entity.ModificadaEm = null;
            var sql = "INSERT INTO Tarefas (Nome, Descricao, Status, ConcluidaEm, CriadaEm, ModificadaEm) " +"Values (@Nome, @Descricao, @Status, @ConcluidaEm, @CriadaEm, @ModificadaEm);";

            var result = await _session.Connection.ExecuteAsync(sql, entity, _session.Transaction);
            return result;
        }

        public async Task<int> Delete(int id)
        {
            var sql = "DELETE FROM Tarefas WHERE Id = @Id;";
            var result = await _session.Connection.ExecuteAsync(sql, new { Id = id }, _session.Transaction);
            return result;
        }

        public async Task<Tarefa> Get(int id)
        {
            var sql = "SELECT * FROM Tarefas WHERE Id = @Id;";
            var result = await _session.Connection.QueryAsync<Tarefa>(sql, new { Id = id }, _session.Transaction);
            return result.FirstOrDefault();
        }

        public async Task<IEnumerable<Tarefa>> GetAll()
        {
            var sql = "SELECT * FROM Tarefas;";
            var result = await _session.Connection.QueryAsync<Tarefa>(sql, _session.Transaction);
            return result;
        }

        public async Task<int> Update(Tarefa entity)
        {
            entity.ModificadaEm = DateTime.Now;
            var sql = "UPDATE Tarefas SET Nome = @Nome, Descricao = @Descricao, Status = @Status, ConcluidaEm =
                           @ConcluidaEm, ModificadaEm = @ModificadaEm WHERE Id = @Id;";


            var result = await _session.Connection.ExecuteAsync(sql, entity, _session.Transaction);
            return result;
        }
    }
}

Implementando o Unit Of Work

Crie a pasta UnitofWork no projeto e nesta pasta crie a interface IUnitofWork e a  seguir a classe concreta Unit_of_Work() que implementa esta interface:

1- IUnitofWork

using ApiTarefas.Repositories;
using System;

namespace ApiTarefas.UnitofWork
{
    public interface IUnitofWork :  IDisposable
    {
        ITarefaRepository Tarefas { get; }
        void BeginTransaction();
        void Commit();
        void Rollback();

    }
}

2- Unti_of_Work

using ApiTarefas.Data;
using ApiTarefas.Repositories;

namespace ApiTarefas.UnitofWork
{
    public class Unit_of_Work : IUnitofWork
    {
        private readonly DBSession _session;
        public ITarefaRepository Tarefas { get; }
        public Unit_of_Work(ITarefaRepository tarefaRepository, DBSession db)
        {
            Tarefas = tarefaRepository;
            _session = db;
        }

        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();
    }
}

Criando o controlador TarefasController

Na pasta Controllers crie o controlador TarefasController com o código abaixo:

using ApiTarefas.Models;
using ApiTarefas.UnitofWork;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ApiTarefas.Controllers
{
    [Route("api/[controller]")]
    [ApiController]

    public class TarefasController : ControllerBase
    {
        private IUnitofWork _uow = null;
        public TarefasController(IUnitofWork unitOfWork)
        {
            _uow = unitOfWork;
        }

        [HttpGet]
        public async Task<IEnumerable<Tarefa>> GetTarefas()
        {
            var result = await _uow.Tarefas.GetAll();
            return result;
        }

        [HttpGet("{id:int}")]
        public async Task<Tarefa> GetTarefa(int id)
        {
            var result = await _uow.Tarefas.Get(id);
            return result;
        }

        [HttpPost]
        public async Task<bool> AddTarefa(Tarefa tarefa)
        {
            _uow.BeginTransaction();
            var result = await _uow.Tarefas.Add(tarefa);
            _uow.Commit();
            return result > 0 ;
        }

        [HttpPut]
        public async Task<bool> UpdateTarefa(Tarefa tarefa)
        {
            _uow.BeginTransaction();
            var result = await _uow.Tarefas.Update(tarefa);
            _uow.Commit();
            return result > 0;
        }

        [HttpDelete("{id:int}")]
        public async Task<bool> DeleteTarefa(int id)
        {
            _uow.BeginTransaction();
            var result = await _uow.Tarefas.Delete(id);
            _uow.Commit();
            return result > 0;
        }
    }
}

Injetamos o serviço UnitOfWork no construtor e com essa instância usamos as operações do repositório pertinente.

Antes de testar temos que registrar os serviços e as dependências no método ConfigureServices da classe Startup:

public void ConfigureServices(IServiceCollection services)
{
            services.AddScoped<DBSession>();
            services.AddTransient<ITarefaRepository, TarefaRepository>();
            services.AddTransient<IUnitofWork, Unit_of_Work>();

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "ApiTarefas", Version = "v1" });
            });
}

Executando o projeto teremos a exibição da interface do Swagger com os endpoints para as operações CRUD:

Podemos agora testar cada um dos endpoints para obter, incluir, alterar e excluir tarefas.

Pegue o projeto aqui:  ApiTarefas.zip (sem as referências)

"Se o SENHOR não edificar a casa, em vão trabalham os que a edificam; se o SENHOR não guardar a cidade, em vão vigia a sentinela."
Salmos 127:1

Referências:


José Carlos Macoratti