C# .Net Core — Criando uma aplicação utilizando Repository Pattern com dois ORMs diferentes Dapper e Entity Framework Core — Parte 1

Olá pessoal, no post de hoje iniciarei uma serie com objetivo de trazer a vocês um exemplo de uma aplicação em Asp.Net Core, desde a modelagem e criação do banco de dados até o desenvolvimento do front-end. Farei o uso do padrão Repository com a utilização de dois ORMs diferentes na plataforma .Net Core 2.1, a aplicação será dividida em camadas de Domínio, Serviços, Infraestrutura e Apresentação, o principal objetivo da serie é apresentar a utilização do ORM Dapper como também do Entity Framework Core. A implementação seguirá de maneira que seja possível substituir a utilização de um ORM pelo outro a partir de uma interface, assinada via injeção de dependência.

Antes de iniciar vamos ao entendimento de alguns pontos importantes, primeiramente do que seria o Repository Pattern.

O padrão Repository tem por objetivo abstrair a camada de acesso ao banco de dados, tornando transparente para a camada de negócio a tecnologia utilizada, como por exemplo, se o banco de dados é relacional como MsSQL, MySQL ou PostgreSQL, ou se é utilizado um banco de dados não relacional como MongoDB, Cosmos DB (Azure), Dynamo DB(Aws). Essa camada também será responsável por todas as operações de CRUD (Create, Read, Update, Delete), já a camada de serviço terá acesso a base dados a partir da implementação destes repositórios.

Vamos ao segundo ponto importante, ORM Object Relational Mapping tem por objetivo facilitar o mapeamento das entidades criadas a partir do paradigma orientado objeto ao banco de dados, seja a partir do auto mapemanto dos dados em operações de CRUD, como também a exemplo do EntityFramework Core tornar possível uma abordagem de desenvolvimento conhecida como Code-First, que seria, primeiro modele seu domínio criando suas classes e propriedades e a partir dessa modelagem o ORM se encarrega de mapear e gerar o banco de dados.

Entendendo a solution e suas camadas

O projeto será divido em 4 camadas, Core, Infrastructure, Presentation e Tests de integração. Cada camada será responsável por expor suas funcionalidades a partir da utilização de Interfaces que terá sua implementação assinada via injeção de dependência com objetivo de tornar nossas classes dependentes de abstrações que serão utilizadas nos construtores.

RepositoryPattern Solution
  • Core, terá nosso domínio que será a modelagem do banco de dados e também a camada de serviço da nossa aplicação fazendo a integração entre o que queremos expor da camada de Infrastructure para a camada Presentation.
  • Infrastructure, será responsável por toda a camada de acesso e operações em banco de dados.
  • Presentation, será uma aplicação Web utilizando MVC (Model View Controller) em Asp.Net Core, sendo o ponto de comunicação entre o usuário final e a aplicação. Poderia ser também uma Api Rest e o desenvolvimento do Front-End poderia ser realizado com a utilização de algum framework javascript como Angular ou uma biblioteca como VueJs ou React.
  • Tests de integração, neste caso optei por criar um projeto de testes de integração e não de testes unitários. O testes de integração terão por objetivo testar as implementações reais dos repositórios, realizando as operações de CRUD sem o uso de MOQ. Como não teremos regras de negócio para essa a aplicação não teria sentido criar testes unitários utilizando MOQ para mocar as interfaces dos repositórios. Um ponto importante que devemos nos atentar, é que a cada execução do projeto de teste, as operações ocorrerão de fato na base de dados e se isso for um problema para seu ambiente de desenvolvimento será necessário criar uma base de dados separada somente para os testes de integração.

Criando o projeto

No Visual Studio 2017 acesse Arquivo -> Novo -> Projeto -> Biblioteca de Classes (.Net Core). Crie duas pastas de solução uma chamada Core e outra de Infrastructure. Botão direito na Solution -> Adicionar -> Nova Pasta Solução. Aconselho excluir o primeiro projeto criado e dentro das pastas de Solução criar um novo projeto de Biblioteca de classes (.Net Core) um chamado Domain em Core, e outro chamado Infrastructure em Infrastructure.

Solution — estrutura inicial

Let’s Code

Para criar as tabelas do banco de dados seguiremos abordagem Code First utilizando o ORM Entity Framework Core. Vamos então criar nossas entidades de domínio.

Classes do domínio

IIdentityEntity.cs será a interface que todas as entidades de domínio deverão fazer referência.

Inteface IIdentityEntity.cs

Criando a classe User.cs.

User.cs

Na entidade User.cs definimos três propriedades, Id, Name e uma coleção de tarefas que é a propriedade TasksToDo, observe que TasksToDo é uma propriedade IReadOnlyCollection, desse modo impossibilitamos que classes externas recriem ou alterem a lista de tarefas fora da classe usuário. Este tipo de abordagem é útil quando queremos tratar regras de negócio para que alguma alteração ocorra no objeto de domínio. Dessa maneira a única forma de adicionar um novo elemento à lista de tarefas é via método da própria classe usuário que poderia realizar N validações antes de adicionar tal elemento. A propriedade TasksToDo também é marcada como virtual que seria para utilização de Lazy Loading que se tornou possível a partir do EF Core 2.1, caso você o ative em sua aplicação, não farei o uso do Lazy Loading neste exemplo mas segue uma breve introdução desta funcionalidade.

De uma maneira sucinta o Lazy Loading possibilita o carregamento tardio de uma propriedade que somente será carregada se realmente for necessário, como por exemplo, quando a propriedade é utilizada. Nesse caso poderia ser uma simples chamada do método Count de TasksToDo, sem o uso do Lazy Loading para carregar as tarefas relacionadas a um usuário é preciso tornar isso explícito no seu Select, como por exemplo fazendo o uso do método Include do EF Core, ao ativar o Lazy Loading o Include deixa de ser necessário.
ToDoList.cs

Na entidade TasksToDo.cs basicamente criamos as propriedades Id, Title, Start, Deadline, Status e criamos duas propriedades de navegação UserId e virtual User vale ressaltar que essas propriedades não são obrigatórias, são somente para facilitar a leitura dos dados caso for necessário carregar os dados do usuário relacionado a uma determinada TaskToDo.

Gerando banco de dados a partir do domínio

Para criação do banco de dados faremos o uso da funcionalidade Migrations do Entity Framework Core. Na pasta de solução Infrastrucuture onde já criaremos uma projeto de classes do .Net Core , criaremos também a seguinte estrutura de pastas e arquivos. Neste primeiro momento nos atentaremos somente a pasta DBConfiguration, EFCore e ao arquivo de configuração appsettings.json.

A classe DatabaseConnection.cs será responsável por carregar o arquivo appsettings.json que contém a string de conexão.

Para fazermos o uso do método .SetBasePath e .AddJsonFile será necessário instalar o pacote do NuGet Microsoft.Extensions.Configuration.Json botão direito no projeto Infrastrucutre -> Gerenciar Pacotes NuGet.

Microsoft.Extensions.Configuration.Json
DatabaseConnection.cs
appsettings.json
Note que estamos utilizando uma conexão no mecanismo de banco de dados localdb, que é uma versão leve do banco de dados SQL Server Express, que normalmente vem instalada com a instalação do Kit de desenvolvimento Desktop para .Net, sendo assim, se você estiver usando o Visual Studio 2017 e nunca ouviu falar sobre este recurso e gostaria de conferir se possui este mecanismo instalado ou não, basta acessar Ferramentas -> Obter Ferramentas e Funcionalidades. abrindo o setup de instalação do Visual Studio, acesse Componentes individuais e procure por SQL Server Express 2016 LocalDB.
SQL Server Express 2016 LocalDB

Criando a classe ApplicationContext.cs, para utilizarmos o Entity Framework Core devemos definir os modelos de dados que vamos utilizar na aplicação, para isso serão definidos dois DbSets um para User e outro para TaskToDo. Para geração das tabelas com Migration e as operações de CRUD, bastaria somente a definição de um dos dois DbSet já que já existe o relacionamento na modelagem da entidades, entretanto prefiro realizar tal definição explicitamente na classe de contexto.

O Entity Framework possui algumas convenções e uma delas é a definição de chaves primarias e estrangeiras, no qual precisamos somente identificar uma a chave como Id ou NomeDaClasseId que o Entity Framework automaticamente saberá quais serão os relacionamentos a serem criados, mas também é possível forçar tais definições realizando o override do método OnModelCreating herdado de DbContext. Para mais informações acesse a documentação da Microsoft.

Para que possamos utilizar o banco de dados MSSQL precisamos instalar o pacote NuGet do Microsoft.EntityFrameworkCore.SqlServer. Com isso habilitamos o método .UseSqlServer.

ApplicationContext.cs

Na classe ApplicationContext.cs foi criado dois métodos construtores um que não recebe nenhuma configuração externa e será utilizado para a criação das migrations sendo o método OnConfiguring responsável por configurar a conexão proveniente da classe DatabaseConnection.cs e outro construtor que poderá ser utilizado por nossa aplicação na camada de Presentation afim de configurarmos externamente o contexto e conexão da aplicação com o banco de dados via injeção de dependência.

Para criarmos o banco de dados e as tabelas a partir da utilização de Migrations, precisaremos adicionar outro pacote NuGet Microsoft.EntityFrameworkCore.Tools.

Microsoft.EntityFrameworkCore.Tools

Uma vez instalada todas as dependências, seu arquivo .csproj deverá estar com as seguintes referências.

Botão direito no projeto Editar Infrastructure.csproj

Gerando a Migração

Compile seu projeto e garanta que seu o arquivo appsettings.json está sendo enviado para o diretório de saída após a compilação.

Botão direito no arquivo appsettings.json -> propriedades

Ainda no Visual Studio acesse o Console do Gerenciador de Pacotes em Ferramentas -> Gerenciador de Pacotes do NuGet -> Console do Genreciador de Pacotes, selecione como projeto padrão Infrastructure\Infrastructure digite o seguinte comando para geração da primeira Migration add-migration InitialTables-User-TaskToDo .

O comando acima irá gerar uma pasta em seu projeto com as definições das tabelas que serão geradas no banco de dados.

Para finalizar e criar o banco de dados e as tabelas execute o seguinte comando no Console do Gerenciador de Pacotes update-database.

Para visualizar o banco de dados criado, acesse Exibir -> Pesquisador de Objetos do SQL Server

Conclusão

Chegamos ao fim da primeira parte do desenvolvimento da aplicação, utilizamos o conceito Code First e a partir da modelagem criada realizamos a criação do banco de dados com o ORM Entity Framework Core e também entendemos quais referências são necessárias para que possamos utiliza-lo com SQL Server e Migrations.

Espero que tenham gostado e essa primeira parte já se encontra em meu GitHub. Fiquem a vontade para fazer o fork do repositório e testar a implementação.

Em breve trarei a segunda parte, no qual criaremos as interfaces genéricas para os repositórios como também realizaremos suas implementações, caso deseje continuar acompanhando essa serie de posts os convido a deixarem sua clap como também a me seguir aqui no Medium.