Blazor - Usando o padrão Repositório - II

Hoje veremos como usar o padrão repositório em uma aplicação Blazor Server para exibir os posts de um blog.

Continuando a primeira parte do artigo vamos implementar o padrão repositório em nosso projeto.  

O padrão repositório ou Repository Pattern permite um encapsulamento da lógica de acesso a dados, permitindo o uso da injeção de dependência (DI) e nos levando a uma visão mais orientada a objetos das interações com a camada de acesso a dados.

Usando este padrão, aplicamos em nossa camada de domínio o princípio da persistência ignorante (PI), e assim nossas entidades da camada de negócio, não devem sofrer impactos pela forma como são persistidas no banco de dados.

Os grandes benefícios ao utilizar o padrão repositório são:

  1. Permitir trocar o banco de dados utilizado sem afetar o sistema como um todo;
  2. Coloca o código centralizado em um único ponto, evitando duplicidade.;
  3. Facilita a implementação de testes unitários;
  4. Diminui o acoplamento entre classes;
  5. Padronização de códigos e serviços;

Existem diversas formas de implementar o padrão e nesta aula eu vou usar a abordagem clássica e implementar um repositório genérico síncrono.

Implementando o padrão repositório

Vamos criar uma pasta Repositories no projeto e nesta pasta vamos implementar o nosso repositório.

Vamos iniciar criando a interface IRepository<T> onde T é uma classe.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace BlogApp.Repositories
{
    public interface IRepository<T> where T : class
    {
        IEnumerable<T> Get();
        IEnumerable<T> Get(Expression<Func<T, bool>> predicate);
        T GetId(int id);
        T GetById(Expression<Func<T, bool>> predicate);
        void Add(T entity);
        void Delete(T entity);
        void Update(T entity);
    }
}

Definimos o contrato na interface IRepository, e, embora tenhamos definidos várias assinaturas de métodos neste exemplo eu não vou implementar o CRUD vou apenas exibir dados, mas vou fazer a implementação de todos os métodos para exemplo.

Agora temos que criar a classe Repository que vai implementar essa interface:

using BlogApp.Data;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace BlogApp.Repositories
{
    public class Repository<T> : IRepository<T> where T : class
    {
        private readonly AppDbContext _context;
        public Repository(AppDbContext context)
        {
            _context = context;
        }
        public void Add(T entity)
        {
            _context.Set<T>().Add(entity);
        }
        public void Delete(T entity)
        {
            _context.Set<T>().Remove(entity);
        }
        public void Update(T entity)
        {
            _context.Entry(entity).State = EntityState.Modified;
            _context.Set<T>().Update(entity);
        }
        public IEnumerable<T> Get()
        {
            return _context.Set<T>().AsEnumerable<T>();
        }
        public T GetId(int id)
        {
            return _context.Set<T>().Find(id);            
        }
        public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
        {
            return _context.Set<T>().Where(predicate).AsEnumerable<T>();
        }
        public T GetById(Expression<Func<T, bool>> predicate)
        {
            return _context.Set<T>().SingleOrDefault(predicate);
        }
    }
}

Note que estamos injetando uma instância do nosso contexto (AppDbContext) no construtor para a seguir usar essa instância e implementar os métodos definidos na interface.

Para poder usar o repositório para as nossas entidades Categoria e Post temos que fazer a implementação específica para essas entidades.

Assim eu poderia começar criando uma classe de Categoria que herda de IRepository<T> mas vamos supor que eu agora precise de um método específico para o Categoria que retorna a lista de categorias pelo nome e outro método específico para Post que retorna todos os Post pelo id da categoria.

Assim vamos criar a interface ICategoriaRepository:

using BlogApp.Data;
using System.Collections.Generic;
namespace BlogApp.Repositories
{
    public interface ICategoriaRepository : IRepository<Categoria>
    {
        IEnumerable<Categoria> GetCategoriasPorNome();
    }
}

Aqui define o contrato para implementar o método GetCategoriasPorNome() que é específico de Categoria.

Vamos fazer a mesma coisa para Post criando a interface IPostRepository

using BlogApp.Data;
using System.Collections.Generic;
namespace BlogApp.Repositories
{
    public interface IPostRepository : IRepository<Post>
    {
        IEnumerable<Post> GetPostsPorCategoriaId(int id);
    }
}

Aqui temos a assinatura do método GetPostsPorCategoriaId() que é especifico para Post.

Agora sim podemos implementar o repositório para Categoria e Post criando as classes CategoriaRepository e PostRepository:

1- CategoriaRepository

using BlogApp.Data;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace BlogApp.Repositories
{
    public class CategoriaRepository : IRepository<Categoria> , ICategoriaRepository
    {
        private readonly AppDbContext _context;
        public CategoriaRepository(AppDbContext context) 
        {
            _context = context;
        }

        public void Add(Categoria entity)
        {
            _context.Categorias.Add(entity);
        }
        public void Delete(Categoria entity)
        {
            _context.Categorias.Remove(entity);
        }
        public IEnumerable<Categoria> Get()
        {
            return _context.Categorias.AsNoTracking().ToList();
        }
        public Categoria GetId(int id)
        {
            return _context.Categorias.Find(id);
        }
        public void Update(Categoria entity)
        {
            _context.Categorias.Update(entity);
        }
        public IEnumerable<Categoria> Get(Expression<Func<Categoria, bool>> predicate)
        {
            return _context.Categorias.Where(predicate);
        }
        public Categoria GetById(Expression<Func<Categoria, bool>> predicate)
        {
            return _context.Categorias.FirstOrDefault(predicate);
        }
        public IEnumerable<Categoria> GetCategoriasPorNome()
        {
            return Get().OrderBy(c => c.Nome).ToList();
        }
    }
}

2- PostRepository

using BlogApp.Data;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace BlogApp.Repositories
{
    public class PostRepository : IRepository<Post>, IPostRepository
    {
        private readonly AppDbContext _context;
        public PostRepository(AppDbContext context)
        {
            _context = context;
        }

        public void Add(Post entity)
        {
            _context.Posts.Add(entity);
        }
        public void Delete(Post entity)
        {
            _context.Posts.Remove(entity);
        }
        public IEnumerable<Post> Get()
        {
            return _context.Posts.AsNoTracking().ToList();
        }
        public Post GetId(int id)
        {
            return _context.Posts.Find(id);
        }
        public void Update(Post entity)
        {
            _context.Posts.Update(entity);
        }
        public IEnumerable<Post> Get(Expression<Func<Post, bool>> predicate)
        {
            return _context.Posts.Where(predicate);
        }
        public Post GetById(Expression<Func<Post, bool>> predicate)
        {
            return _context.Posts.FirstOrDefault(predicate);
        }
        public IEnumerable<Post> GetPostsPorCategoriaId(int id)
        {
            return _context.Posts.Where(p => p.CategoriaId == id);
        }
    }
}

Na figura abaixo temos uma representação das interfaces e classes que foram criadas para implementar o padrão reposítório usando uma abordagem padrão:

Na próxima parte do artigo vamos criar os componentes Blazor para exibir o menu de categorias e os posts relacionados com a categoria selecionada.

Pegue o projeto aqui:   BlogApp.zip (sem as referências)

"E se abrires a tua alma ao faminto, e fartares a alma aflita; então a tua luz nascerá nas trevas, e a tua escuridão será como o meio-dia."
Isaías 58:10
 


Referências:


José Carlos Macoratti