ASP.NET Core - Dapper com Repository Pattern e Unit Of Work
Neste tutorial, veremos como usar o Dapper na ASP.NET Core junto com um repositório genérico usando a Onion Architecture. |
O Dapper é uma biblioteca de mapeamento objeto-relacional (ORM) de código aberto para .NET, que permite trabalhar com bancos de dados relacionais usando objetos e métodos familiares do C#.
|
Ao contrário de outros ORMs que abstraem completamente o SQL, o Dapper trabalha com o SQL diretamente, permitindo que o desenvolvedor escreva consultas SQL personalizadas e otimizadas, em vez de depender de consultas geradas automaticamente pelo ORM. O Dapper é conhecido por sua alta performance, pois utiliza técnicas de mapeamento de dados de baixo nível e é otimizado para minimizar a sobrecarga de processamento.
Vamos mostrar como usar o Dapper em um projeto ASP .NET Core em um repositório genérico e também vamos implementar o padrão Unit Of Work. Seguindo as boas práticas vamos usar a arquitetura Cebola ou Onion no projeto onde vamos gerenciar informações de produtos.
A Onion Architecture (também conhecida como Arquitetura Cebola) é um padrão de arquitetura de software que propõe uma abordagem em camadas para o desenvolvimento de aplicações. A ideia é que cada camada da arquitetura seja independente das outras, sendo que as camadas internas contêm a lógica de negócios e as externas fornecem interfaces para outras camadas ou para o mundo exterior.
A arquitetura é chamada de "Cebola" porque as camadas são organizadas em círculos concêntricos, como as camadas de uma cebola. Na camada mais interna, temos as entidades de negócios e a lógica de domínio, que representam o núcleo da aplicação. Em seguida, há camadas de serviços de aplicação, interfaces de usuário, infraestrutura e, finalmente, a camada externa, que representa o ambiente externo à aplicação, como bancos de dados, sistemas de arquivos e outros serviços externos.
recursos usados:
Criando o banco de dados no SQL Server
Vamos criar o banco de dados Cadastro e a tabela
Produtos que iremos usar no projeto no SQL Server
usando o SQL Server Management Studio :
1- Abra o SQL Management Studio e conecte-se ao seu banco de dados local (SQL Express)
2- Clique com o botão direito do mouse na pasta Bancos de Dados e selecione Novo Banco de Dados...
3- Digite um nome de Banco de Dados - Cadastro - e finalize clicando em OK.
Clique com o botão direito do mouse em seu novo banco de dados e selecione Nova consulta.
Na janela em branco vamos executar o comando SQL para criar tabela Produtos conforme o código abaixo:
CREATE
TABLE
Produtos( Id int IDENTITY(1,1) NOT NULL, Nome nvarchar(50) NOT NULL, CodigoBarras nvarchar(50) NOT NULL, Descricao nvarchar(max) NOT NULL, Preco decimal(18, 2) NOT NULL, IncluidoEm datetime NOT NULL, ModificadoEm datetime NULL, CONSTRAINT PK_Products PRIMARY KEY CLUSTERED ( Id ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO |
A seguir clique em Executar para criar a tabela.
Ao final você deverá ver no seu banco de dados a tabela Produtos com a seguinte estrutura:
Criando o projeto
Abra o VS 2022 Community e crie um novo projeto usando o template Blank Solution com o nome ApiDapper.
A seguir vamos incluir na solução os seguinte projetos usando o template Class Library :
Para concluir inclua o projeto WEB API na solução usando o template ASP.NET COre Web API:
Com as seguintes configurações:
Ao final teremos a solução e os projetos conforme mostrado abaixo:
Para simplificar o projeto a camada Application que deveria conter os serviços que orquestram os objetos do domínio contém apenas as pastas Services e Interfaces onde definimos um esboço do serviço que poderia ser usado no futuro.
As interfaces para produto, repositório e unit of work foram definidas na pasta Interfaces da camada Domain pois são parte da infraestrutura necessária para que a camada de domínio possa se comunicar com a camada de persistência de dados sem violar a separação de responsabilidades e a abstração do domínio.
Dessa forma, a implementação dos repositórios e da unidade de trabalho deve ser feita na camada de infraestrutura, e as interfaces devem ser definidas na camada de domínio para que a camada de aplicação possa utilizá-las sem ter que conhecer os detalhes da implementação na camada de infraestrutura.
Desta forma na camada de aplicação, você pode definir classes que implementam os casos de uso da aplicação. Essas classes fazem uso das interfaces dos repositórios e da unidade de trabalho definidos na camada de domínio para interagir com a camada de persistência de dados implementada na camada de infraestrutura.
Como o objetivo do artigo é mostrar o uso do Dapper eu não vou fazer essa implementação na camada Application.
No projeto Domain vamos criar a pasta Entities e nesta pasta criar a classe Produto:
public class Produto { public int Id { get; set; } public string? Nome { get; set; } public string? Descricao { get; set; } public string? CodigoBarras { get; set; } public decimal Preco { get; set; } public DateTime IncluidoEm { get; set; } public DateTime ModificadoEm { get; set; } } |
Neste artigo, para simplificar o projeto eu não estou criando um construtor na entidade, não estou definindo os acessores set como private e também não estou fazendo a validação do domínio. Esses detalhes devem sempre ser levados em conta em um projeto completo e de produção.
Vamos criar a pasta Interfaces na camada Domain e criar nesta pasta as interfaces IRepository, IProdutoRepository e IUnitOfWork :
1- IRepository
public interface IRepository<T>
where T : class { Task<T> GetPorIdAsync(int id); Task<IReadOnlyList<T>> GetTodosAsync(); Task<int> AdicionarAsync(T entity); Task<int> AtualizarAsync(T entity); Task<int> DeletarAsync(int id); } |
2- IProdutoRepository
public interface IProdutoRepository : IRepository<Produto> { } |
3- IUnitOfWork
public interface IUnitOfWork { IProdutoRepository Produtos { get; } } |
O projeto Domain não deve conter referêcia a nenhum projeto.
Na camada Application vamos apenas criar as pastas Interfaces e Services e nestas pastas vamos criar a interface IProdutoService e a classe concreta ProdutoService sem implementação para simplificar a abordagem como já foi explicado.
Na camada Infrastructure vamos incluir os seguintes pacotes Nuget:
O projeto Infrastructure dever conter uma referência ao projeto Domain.
A seguir vamos criar a pasta Repositories e nesta classe criar as seguintes classes:
1- ProdutoRepository
using
ApiDapper.Domain.Abstractions; using ApiDapper.Domain.Entities; using Dapper; using Microsoft.Extensions.Configuration; using System.Data.SqlClient; namespace ApiDapper.Infrastructure.Repositories;
public class ProdutoRepository : IProdutoRepository |
2- UnitOfWork
using ApiDapper.Domain.Abstractions;
namespace ApiDapper.Infrastructure.Repositories;
public class UnitOfWork : IUnitOfWork
{
public UnitOfWork(IProdutoRepository productRepository)
{
Produtos = productRepository;
}
public IProdutoRepository Produtos { get; }
}
|
Aqui temos que a classe UnitOfWork está explicitamente acoplada ao IProdutoRepository. Isso significa que, se você decidir adicionar mais repositórios no futuro (por exemplo, IClienteRepository, IVendaRepository, etc.), precisará modificar a classe UnitOfWork para injetar esses repositórios adicionais e isso viola o princípio Open-Closed do SOLID, que preconiza que as classes devem estar abertas para extensão, mas fechadas para modificação.
Assim uma melhoria para esta implementação seria usar a técnica de injeção de dependência automática. Isso pode ser feito configurando o contêiner de injeção de dependência para registrar automaticamente os repositórios necessários no escopo da unidade de trabalho.
Na camada ApiProdutos vamos criar na pasta Controllers o controlador ProdutosController :
using
ApiDapper.Domain.Abstractions; using ApiDapper.Domain.Entities; using Microsoft.AspNetCore.Mvc; namespace ApiProdutos.Controllers;
[Route("api/[controller]")]
public ProdutosController(IUnitOfWork unitOfWork) |
Este projeto deverá ter uma referência aos projetos Domain e Infrastructure.
Agora só falta definir a string de conexão no arquivo appsettings.json:
"ConnectionStrings":
{
"DefaultConnection":
"Data
Source=.;Initial Catalog=Cadastro;Integrated
Security=True;TrustServerCertificate=True;"
}, |
Executando o projeto iremos obter o resultado abaixo:
Vamos criar alguns produtos acionando o endpoint Post api/Produtos :
A seguir vamos acionar o endpoint GET api/produtos :
Podemos também atualizar e excluir um produto usando os endpoints implementados com a ajuda do Dapper e usando os padrões Repository e Unit Of Work.
Pegue o projeto aqui : ApiDapper.zip (sem as referências)
E estamos conversados...
"Uns confiam em carros e outros em cavalos, mas nós faremos menção do nome do
Senhor nosso Deus."
Salmos 20:7
Referências:
NET - Unit of Work - Padrão Unidade de ...