ASP .NET MVC - CRUD com Repositório Genérico


  Neste artigo vamos , mais uma vez, apresentar os conceitos relacionados ao padrão repositório e ao padrão Unit Of Work com um CRUD em uma aplicação ASP .NET MVC 5 usando a linguagem C#.

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 dados e mapeia essa lógica para entidades na lógica de negócio. Ele trabalhar 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."

(http://martinfowler.com/eaaCatalog/repository.html)

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 através da Unit of Work ao invés de acessar diretamente o Entity Framework.

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 esta baseada em uma arquitetura em camadas onde teremos os seguintes projetos:

  1. Livraria.Core - Contém as entidades e 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 :

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;

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:

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

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

No menu File clique em Add -> Project e 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;

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.

Nesta 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 persistência dos dados na camada de acesso a dados.

lembre-se 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:

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

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() - Este 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 o critério informado em tempo de execução via expressão lambada. Estamos usando o delegate Func, e aplicando o predicate para verificar se o dado atende o 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 o critério informado via expressão lambda. Usamos novamente o delegate Func e aplicamos o predicate para verificar se o dado atende o 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. Nesta 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 esta entidade para uma tabela Usuarios existente em um banco SQL Server chamado Cadastro.mdf.

Abaixo vemos a estrutura da tabela Usuarios:

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

Defina o código baixo nesta classe:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
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 através do DbSet.

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

Defina o código baixo nesta classe:

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

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

O último detalhe que não podemos esquecer e 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 a figura 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 representam 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 a seguir 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 a seguir 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:

Vou parar por aqui, agora cabe a você criar a camada de negócios da aplicação como um exercício.

Para ajudar vou deixar os links de dois artigos onde eu trato do assunto:

Porque a lei foi dada por Moisés; a graça e a verdade vieram por Jesus Cristo.
Deus nunca foi visto por alguém. O Filho unigênito, que está no seio do Pai, esse o revelou.
João 1:17,18

Referências:


José Carlos Macoratti