.NET

27 mai, 2016

ASP .NET MVC – CRUD com Repositório Genérico e Unit of Work

Publicidade

Neste artigo, volto a falar sobre o padrão repositório e o padrão Unit of Work aplicando na prática os conceitos no desenvolvimento de uma aplicação que realiza um CRUD básico nas informações de livros.

Nosso modelo de domínio, portanto, será a entidade chamada de Livro que iremos detalhar no decorrer do artigo.

A aplicação desenvolvida vai usar os recursos do BootStrap e do JavaScript para melhorar a experiência do usuário.

Para tornar o entendimento mais simples, irei trabalhar com uma única entidade Livro.

Vou definir um repositório genérico para todas as entidades e uma classe UnitOfWork, que vai criar uma instância do nosso repositório para cada entidade, sendo que o repositório será usado para realizar as operações CRUD – Create, Read, Update e Delete.

O padrão Repository separa a lógica de acesso a dados e mapeia essa lógica para entidades na lógica de negócio. Ele trabalha com as entidades de domínio e realiza a lógica de acesso a dados.

No padrão Repository, as entidades de domínio e a lógica de acesso a dados se comunicam usando interfaces, e isso esconde os detalhes do acesso a dados da camada de negócios.

Martin Fowler afirma: “O padrão  Repository faz a mediação entre o domínio e as camadas de mapeamento de dados, agindo como uma coleção de objetos de domínio em memória…
Conceitualmente, um repositório encapsula o conjunto de objetos persistidos em um armazenamento de dados e as operações realizadas sobre eles, fornecendo uma visão mais orientada a objetos da camada de persistência… e também dá suporte ao objetivo de alcançar uma separação limpa e uma forma de dependência entre o domínio e as camadas de mapeamento de dados”.

Vamos criar uma instância da classe UnitOfWork no controlador da aplicação ASP .NET MVC e, então, criar uma instância do repositório dependendo da entidade e, posteriormente, usar os métodos do repositório conforme as operações CRUD.

No diagrama abaixo, temos o relacionamento entre o repositório e o contexto do Entity Framework; os controladores MVC interagem com o repositório por meio da Unit of Work em vez de acessarem diretamente o Entity Framework.

asp-1

Vamos definir a classe UnitOfWork e criar uma instância dessa classe de forma que a Unit Of Work irá instanciar o nosso DbContext e, a seguir, cada instância do repositório usa o mesmo DbContext para realizar as operações no banco de dados. Dessa forma, a Unit of Work é o padrão que garante que todos os repositórios irão usar o mesmo contexto do banco de dados.

Vamos utilizar o Entity Framework em uma abordagem Code First.

Nossa aplicação está baseada em uma arquitetura em camadas na qual teremos os seguintes projetos:

  1. Livraria.Core – Contém as entidades e o nosso domínio
  2. Livraria.Dados – Contém o DataContext, o mapeamento da entidade, o repositório e a Unit of Work
  3. Livraria.Web – Contém a interface com usuário representada pela aplicação ASP .NET MVC

Recursos usados:

  • Visual Studio Community 2015

Criando a solução e os projetos da aplicação

Abra o VS 2015 Community e clique em New Project.

A seguir, selecione Other Project Types -> Visual Studio Solutions.

Informe o nome Livraria e clique no botão OK.

asp-2

No menu File, clique em Add -> New Project e selecione o template Class Library.

Informe o nome Livraria.Core e clique em OK.

Repita o processo acima e crie o projeto Livraria.Dados.

Por último, clique no menu File e em Add -> New Project.

Selecione Web -> ASP .NET Web Applications e informe o nome Livraria.Web e clique no botão OK.

A seguir, selecione o template MVC conforme mostra a figura abaixo:

asp-3

Será criada uma solução contendo os projetos e a aplicação MVC contendo toda a estrutura de pastas criadas pelo framework ASP .NET MVC:

asp-4

Vamos incluir um novo projeto do tipo Class Library em nossa solução.

No menu File, clique em Add -> Project, selecione o template Class Library e informe o nome Mvc_Repositorio.Dominio.

Vamos incluir uma referência ao Entity Framework neste projeto via Nuget.

No menu TOOLS, clique em Nuget Package Manager e a seguir em Manage Nuget Packages for Solution.

asp-5

Selecione o Entity Framework e clique no botão Install escolhendo para ser instalado somente no projeto Mvc_Repositorio.Dominio.

Apague o arquivo Class1.cs criado por padrão e, a seguir, crie uma pasta chamada Repositorio no projeto Mvc_Repositorio.Dominio. Nessa pasta, vamos criar a nossa interface do Repositorio.

No menu PROJECT, clique em Add New Item.

Selecione o template Interface e informe o nome IRepositorio.cs

Agora vamos definir os métodos na nossa interface que deverão ser implementados para realizar o acesso e a persistência dos dados na camada de acesso a dados.

Lembre-se de que uma interface é um contrato que define como uma classe deve ser implementada, assim vamos definir assinaturas de métodos que deverão implementados por qualquer classe que desejar usar a nossa interface.

Abaixo, vemos os métodos definidos na nossa interface IRepositorio:

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public interface IRepositorio<T> where T : class
    {
        IQueryable<T> GetTodos();
        IQueryable<T> Get(Expression<Func<T, bool>> predicate);
        T Procurar(params object[] key);
        T Primeiro(Expression<Func<T, bool>> predicate);
        void Adicionar(T entity);
        void Atualizar(T entity);
        void Deletar(Func<T, bool> predicate);
        void Commit();
        void Dispose();
    }
}

Vamos entender o código acima:

1 – Note que estamos usamos o namespace using System.Linq.Expressions, que contém classes e enumerações que permitem representar expressões de código no nível da linguagem como objetos na forma de árvores de expressões;
2 – Na assinatura da classe, estamos declarando public interface IRepositorio<T> where T : class; aqui, T é uma classe;
3 – IQueryable<T> GetTodos() – esse método retorna todos os dados como IQueryable; dessa forma, podemos retornar a lista e aplicar expressões lambdas para filtrar e classificar os dados;
4 – IQueryable<T> Get(Expression<Func<T, bool>> predicate) – retorna os dados que atendem ao critério informado em tempo de execução via expressão lambda. Estamos usando o delegate Func, e aplicando o predicate para verificar se o dado atende ao critério (retorna true ou false);
5 – T Find(params object[] key) – recebe um array de objetos e efetua a pesquisa pela chave primária;
6 – T First(Expression<Func<T, bool>> predicate) – retorna o primeiro dado que atende ao critério informado via expressão lambda. Usamos novamente o delegate Func e aplicamos o predicate para verificar se o dado atende ao critério;
7 – void Adicionar(T entity) – recebe o objeto T para realizar a inclusão no banco de dados;
8 – void Atualizar(T entity) – recebe o objeto T para realizar a atualização no banco de dados;
9 – void Deletar(<Func<T, bool>> predicate) – excluir registros usando uma condição definida na expressão lambda (via delegate Func) e aplicando o predicate (retorna true ou false) para verificar o critério;
10 – void Commit() – chama o método ChaveChanges() do contexto para efetivar todas as alterações realizadas no contexto. Ao final de cada operação, você deve sempre chamar este método para efetivar as operações que foram feitas na memória no banco de dados. Se não fizer isso, irá perder todas as operações realizadas;
11 – void Dispose() – executa a limpeza dos objetos.

Observe que não temos nenhum comando SQL, nenhuma declaração de objetos ADO .NET como connection, command, dataset, datareader etc.

Já temos o contrato definido e agora vamos definir a classe que irá implementar esse contrato.

Antes de implementar a interface IRepositorio, vamos definir o nosso domínio.

Definindo o domínio

Vamos criar uma pasta chamada Entidades no projeto Mvc_Repositorio.Dominio.

Selecione o projeto e, no menu PROJECT, clique em New Folder e informe o nome Entidades. Nessa pasta, vamos definir as entidades do nosso domínio.

Para tornar as coisas bem simples, eu vou definir apenas uma entidade chamada Usuario e vou mapear essa entidade para uma tabela Usuarios existente em um banco SQL Server chamado Cadastro.mdf.

Abaixo vemos a estrutura da tabela Usuarios:

asp-6

Selecione a pasta criada e, no menu PROJECT, clique em Add Class e informe o nome Usuario.

Defina o código baixo nessa classe:

namespace Mvc_Repositorio.Dominio.Entidades
{
    [Table("Usuarios")]
    public class Usuario
    {
        [Key]
        public int UsuarioId { get; set; }
        [Required(ErrorMessage = "Informe o login do usuário.")]
        [Display(Name = "Usuário")]
        public string Nome { get; set; }
        [Required(ErrorMessage = "Informe a senha do usuário.")]
        [DataType(DataType.Password)]
        public string Senha { get; set; }
        [Required(ErrorMessage = "Informe o email do usuário.")]
        public string Email { get; set; }
    }
}

O código acima usa os atributos do Data Annotations para definir o mapeamento para a tabela Usuarios e restrições de validações que deverão ser aplicadas na renderização das views que iremos criar.

Agora, para realizar o mapeamento ORM, vamos definir uma classe onde iremos usar os recursos do Entity Framework por meio do DbSet.

Selecione a pasta criada e, no menu PROJECT, clique em Add Class e informe o nome Usuario.

Defina o código baixo nessa classe:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

using System.Data.Entity;

namespace Mvc_Repositorio.Dominio.Entidades
{
    public class UsuarioContexto : DbContext
    {
         public UsuarioContexto()
            : base("name=ConexaoUsuarios")
        { }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            Database.SetInitializer<UsuarioContexto>(new CreateDatabaseIfNotExists<UsuarioContexto>());
        }

        public DbSet<Usuario> Usuarios { get; set; }
    }
}

Nessa classe, usamos o DbContext e realizamos o mapeamento da entidade Usuario com a tabela Usuarios.

O último detalhe que não podemos esquecer é definir no arquivo web.config da aplicação web a string de conexão  ConexaoUsuarios do banco de dados Cadastro.mdf.

Implementando a interface IRepositorio na classe Repositorio

Selecione a pasta Repositorio do projeto de Dominio e selecione o menu PROJECT – clique em Add New Item.

Selecione o template Class, informe o nome Repositorio.cs e clique no botão Add.

Vamos definir a assinatura da classe Repositorio conforme abaixo:

using Mvc_Repositorio.Dominio.Entidades;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public class Repositorio<T> : IRepositorio<T> ,  IDisposable where T : class
    {
        private UsuarioContexto Context;

        protected Repositorio()
        {
            Context = new UsuarioContexto();
        }

        public IQueryable<T> GetTodos()
        {
            return Context.Set<T>();
        }

        public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
        {
            return Context.Set<T>().Where(predicate);
        }

        public T Procurar(params object[] key)
        {
            return Context.Set<T>().Find(key);
        }

        public T Primeiro(Expression<Func<T, bool>> predicate)
        {
            return Context.Set<T>().Where(predicate).FirstOrDefault();
        }

        public void Adicionar(T entity)
        {
            Context.Set<T>().Add(entity);
        }

        public void Atualizar(T entity)
        {
            Context.Entry(entity).State = EntityState.Modified;
        }

        public void Deletar(Func<T, bool> predicate)
        {
            Context.Set<T>()
           .Where(predicate).ToList()
           .ForEach(del => Context.Set<T>().Remove(del));
        }

        public void Commit()
        {
            Context.SaveChanges();
        }

        public void Dispose()
        {
            if (Context != null)
            {
                Context.Dispose();
            }
                GC.SuppressFinalize(this);
            }
        }
   }

Dessa forma, como já temos o nosso repositório criado, vamos definir agora as interfaces para representarem o repositório específico para a entidade Usuario.

Então, selecione a pasta Repositorio e, no menu PROJECT, clique em Add New Item.

Selecione o template Interface, informe o nome IUsuarioRepositorio.cs e clique no botão Add.

A seguir, defina o código abaixo para a interface IUsuarioRepositorio:

using Mvc_Repositorio.Dominio.Entidades;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public interface IUsuarioRepositorio : IRepositorio<Usuario>
    {
    }
}

Deveremos criar também a classe que implementa a interface IUsuarioRepositorio.

Então, selecione a pasta Repositorio e, no menu PROJECT, clique em Add New Item.

Selecione o template Class, informe o nome UsuarioRepositorio.cs e clique no botão Add.

A seguir, defina o código abaixo para a classe UsuarioRepositorio:

using Mvc_Repositorio.Dominio.Entidades;

namespace Mvc_Repositorio.Dominio.Repositorio
{
    public class UsuarioRepositorio :  Repositorio<Usuario>, IUsuarioRepositorio
    {
    }
}

Observe que não precisamos definir nenhum código nas interfaces e classes acima, pois estamos usando o mecanismo da herança e da implementação da interface e, assim, estamos usando os métodos definidos na interface IRepositorio e na classe Repositorio. Note também que estamos usando as entidades que foram separadas no projeto de Dominio.

Neste momento, nossa solução tem a seguinte estrutura:

asp-7

Na próxima parte do artigo, iremos criar a camada de negócios da nossa aplicação.