.NET 5 + Dapper: exemplos de implementação
Neste post trago alguns exemplos de utilização de Dapper com .NET 5 e ASP.NET Core, procurando com isto sanar algumas dúvidas recorrentes a respeito desta biblioteca de acesso a bases de dados relacionais. As aplicações aqui listadas demonstram como realizar implementações de relacionamentos um-para-um e um-para-muitos, o uso de métodos assíncronos, além do logging de queries e até mesmo a execução de Migrations.
E aproveito este espaço para um convite…
Procurando recolocação profissional? Ou então alavancar sua carreira, aprendendo tecnologias em alta? Que tal então aproveitar este momento em que estamos todos em casa fazendo um treinamento ONLINE e GRATUITO sobre o build e deployment automatizados de aplicações utilizando GitHub Actions?
Aproveite então esta oportunidade e participe da master class sobre GitHub Actions, a ser realizada pelo Azure na Prática na tarde do dia 16/01/2021 (sábado) das 14:00 às 18:00 — horário de Brasília.
Serão abordados ao longo do evento conceitos básicos, além das diferentes possibilidades oferecidas pelo GitHub Actions. Tudo isso saindo do zero, indo até o deployment em ambientes em nuvem e on-premise.
Faça logo sua inscrição no link a seguir, não deixe de indicar o evento para amigos, amigas e colegas de trabalho, além de compartilhar nas redes sociais (teremos emissão de certificado para os participantes!):
Exemplos de implementação
Os diversos exemplos que mencionei foram disponibilizados nos seguintes repositórios do GitHub:
- renatogroffe /ASPNETCore5-SQLServer-Dapper-FluentMigrator-AppInsights: API REST para consulta a indicadores econômicos e que inclui um exemplo de uso de Migration;
- renatogroffe/DotNet5-ConsoleApp-SqlServer-FluentMigrator-DadosGeograficos: Console Application para a execução de Migration criando o banco de dados de regiões e estados;
- ASPNETCore5-SQLServer-Dapper_One-to-One: API REST que demonstra a implementação de relacionamentos do tipo um-para-um, através de uma consulta a estados (com suas respectivas regiões). Os dados desta aplicação foram gerados através do projeto que está em renatogroffe/DotNet5-ConsoleApp-SqlServer-FluentMigrator-DadosGeograficos;
- ASPNETCore5-SQLServer-Dapper_One-to-Many: neste repositório há uma API REST utilizando os mesmos dados consumidos por ASPNETCore5-SQLServer-Dapper_One-to-One, só que desta vez demonstrando a implementação de relacionamentos um-para-muitos (uma consulta de regiões com seus respectivos estados).
O básico sobre Dapper
Importante destacar que o Dapper não pode ser classificado como um ORM, já que não dispõe de inúmeras funcionalidades que integram o Entity Framework Core e o NHibernate. Considerado um micro-ORM, o Dapper pode facilmente ser associado a qualquer tecnologia de banco de dados relacional compatível com o ADO.NET (mecanismo básico de acesso a dados relacionais da plataforma .NET).
Na listagem a seguir podemos observar a utilização do package Dapper na prática (exemplo presente no repositório renatogroffe /ASPNETCore5-SQLServer-Dapper-FluentMigrator-AppInsights):
- Métodos genéricos como Query e QueryFirstOrDefault são associados a instâncias baseadas no tipo IDbConnection (um bom exemplo está na classe SqlConnections, a qual se encontra no namespace Microsoft.Data.SqlClient);
- O resultado destes métodos é convertido então para os tipos informados como parâmetro em Query e QueryFirstOrDefault.
Dapper + Migrations? Sim, é possível com FluentMigrator!
Migrations representam uma opção para que Desenvolvedores implementem as estruturas de um banco de dados relacional sem inumeráveis scripts, utilizando para isto código escrito em suas linguagens de preferência. Como soluções deste tipo costumam suportar múltiplas soluções de bancos de dados, temos ainda a possibilidade de rapidamente criar os mesmos objetos em diferentes tecnologias.
Embora o Dapper não conte nativamente com um mecanismo para a implementação e execução de Migrations, o FluentMigrator é uma excelente alternativa open source que pode ser utilizada em conjunto com esta biblioteca de acesso a dados.
No repositório renatogroffe/DotNet5-ConsoleApp-SqlServer-FluentMigrator-DadosGeograficos temos um exemplo de Console Application (BaseDadosGeograficos) empregando o FluentMigrator e que criará as tabelas de regiões e estados, incluindo chaves primárias + estrangeiras e fará ainda um input inicial de dados.
Foram adicionados a este projeto de exemplo 3 packages do FluentMigrator (além do provider ADO.NET de acesso ao SQL Server):
- FluentMigrator
- FluentMigrator.Runner
- FluentMigrator.Runner.SqlServer
Na classe DadosGeograficosMigration_v1 temos uma primeira implementação de Migrations baseadas no FluentMigrator:
- Este tipo deve herdar da classe básica Migration, sobrescrevendo os métodos Up e Down;
- A classe DadosGeograficosMigration_v1 foi marcada ainda com o atributo Migration, o qual recebe um valor numérico que identifica a mesma e determina a ordem de execução na eventualidade de existirem diferentes Migrations no projeto (além de servir de base para um log de execuções);
- Com a propriedade Create temos o acesso a métodos para a definição da estrutura de tabelas e a criação de foreign keys;
- Já a propriedade Insert permite que se efetue uma carga inicial de dados (como exemplificado nos métodos InsertRegiao e InsertEstado).
Precisamos configurar isso através do uso de uma instância de IServiceCollection (namespace Microsoft.Extensions.DependencyInjection), invocando na sequência os métodos AddFluentMigratorCore e ConfigureRunner (como exemplificado na classe Startup):
A classe Program deste projeto espera que a Connection String ao SQL Server seja informada via comando dotnet run, configurando um objeto do tipo ServiceCollection e acionando a execução da classe ConsoleApp:
E finalmente o construtor de ConsoleApp receberá uma instância de IMigrationRunner (namespace FluentMigrator.Runner), acionando a partir deste objeto o método MigrateUp para a execução da Migration:
Ao acionar esta aplicação com o comando:
dotnet run <CONNECTION STRING>
Teremos como resultado uma série de mensagens indicando os diversos comandos executados através das Migrations. Seguem alguns exemplos:
Além das estruturas dbo.Regioes e dbo.Estados, teremos ainda a tabela dbo.VersionInfo contendo o histórico de execução das Migrations:
Expandindo os objetos dbo.Regioes e dbo.Estados a partir do Azure Data Studio notamos que os mesmos foram criados com as especificações indicadas na classe DadosGeograficosMigration_v1:
E consultas a essas tabelas mostrarão que os dados iniciais foram inseridos com sucesso:
E como ficaria uma implementação equivalente numa aplicação ASP.NET Core?
No repositório renatogroffe /ASPNETCore5-SQLServer-Dapper-FluentMigrator-AppInsights isso foi exemplificado em uma API REST, com um código bastante similar àquele observado na Console App.
Na listagem a seguir temos um exemplo de implementação de Migration:
Já na classe Startup o FluentMigration está sendo configurado por meio de uma chamada ao método AddFluentMigratorCore. Uma instância de IMigrationRunner é recebida via injeção de dependências em Configure, com o acionamento de MigrateUp levando à execução da Migration:
A próxima imagem mostra a API REST em execução logo após os processamentos realizados pelo FluentMigrator:
Relacionamentos um-para-um, programação assíncrona…
No repositório ASPNETCore5-SQLServer-Dapper_One-to-One temos a API REST com os tipos Estado e Regiao, no qual se nota um relacionamento um-para-um (graças à propriedade DadosRegiao em Estado):
Além da implementação da classe EstadosRepository:
- O método Get pode ou não receber um parâmetro, que servirá de base para o filtro do resultado da query. Nas linhas 35 e 36 tal filtro é configurado, com o uso do parâmetro @Sigla (jamais concatene valores, sempre use parâmetros a fim de evitar problemas de segurança como SQL Injection);
- Na linha 41 nota-se o uso do método QueryAsync. As diferentes extensões oferecidas pelo Dapper contam com versões síncronas e assíncronas (Query e QueryAsync constituem bons exemplos);
- No parâmetro map de QueryAsync os dados da região são associados ao estado, lembrando que a consulta em questão envolve um INNER JOIN entre dbo.Estados e dbo.Regioes.
Serão as Actions de EstadosController que farão uso de EstadosRepository:
Eis o resultado de uma consulta a todos os estados:
E a um estado em específico (São Paulo, neste caso):
Application Insights: logging de queries geradas
O Application Insights é uma solução de monitoramento de Web Apps que integra o Microsoft Azure, tendo como uma de suas capacidades a possibilidade de registrar os comandos SQL gerados quando dos acessos a bases de dados relacionais (algo extremamente útil, sem sombra de dúvidas).
Para ativar o uso deste serviço em uma aplicação ASP.NET Core é necessário adicionar à mesma o package Microsoft.ApplicationInsights.AspNetCore (considerando a API REST implementada em ASPNETCore5-SQLServer-Dapper_One-to-One):
Na classe Startup acionar os métodos AddApplicationInsightsTelemetry (informando a este uma chave de instrumentação) e ConfigureTelemetryModule (namespace Microsoft.ApplicationInsights.DependencyCollector) em ConfigureServices:
A requisição envolvendo a consulta a um estado específico será registrada, incluindo o comando SQL executado via Dapper:
Para saber mais sobre o Application Insights acesse o artigo a seguir:
ASP.NET Core + Dapper + EF Core + Application Insights: logando automaticamente comandos SQL
Relacionamentos um-para-muitos
Embora uma solução bastante limitada quando comparada àquilo que o Entity Framework oferece em termos de relacionamentos entre objetos/tabelas, podemos utilizar o package Slapper.AutoMapper com Dapper para a implementação de relacionamentos um-para-muitos (por mais que isto não possibilite hierarquias com vários níveis).
O exemplo abordado nesta seção foi disponibilizado no repositório ASPNETCore5-SQLServer-Dapper_One-to-Many.
Considerando os tipos Regiao e Estado (este último vinculado a Regiao por meio da propriedade Estados):
Na classe RegioesRepository foi definido o método GetAll:
- Convertendo o resultado QueryAsync para um objeto dinâmico (uso da palavra-chave dynamic), as chamadas à classe AutoMapper (namespace Slapper) permitirão indicar cada classe do relacionamento e sua chave correspondente (via método AddIdentifier);
- Na consulta informada a QueryAsync deve-se notar a presença de aliases iniciando por Estados_, correspondentes à propriedade que implementa o relacionamento um-para-muitos entre regiões e aos campos contendo os dados de seus respectivos estados.
Na listagem a seguir está o tipo RegioesController, correspondendo à implementação da API para consulta de regiões e seus respectivos estados (utilizando a classe RegioesRepository):
Na próxima imagem temos o resultado da implementação do relacionamento um-para-muitos com Dapper + Slapper:
Referências
StackExchange/Dapper: Dapper - a simple object mapper for .Net
Fluent migrations framework for .NET | FluentMigrator documentation
Dapper + .NET Core 3.0: exemplos utilizando o novo provider SQL
ASP.NET Core: dicas úteis para o dia a dia de um Desenvolvedor — Parte 6
Dapper: exemplos em .NET Core 2.1 e ASP.NET Core 2.1
Dapper: exemplos de utilização em ASP.NET Core e .NET Full
Dapper: exemplos em .NET Core 2.0 e ASP.NET Core 2.0
Dapper: relacionamentos Um-para-Um e Um-para-Muitos (exemplos em ASP.NET Core)