Desenvolvimento - Visual Basic .NET
Entity Framework 4: Repositório Genérico
O Repository Pattern é um padrão conhecido e que consiste em persistir o estado das entidades no banco de dados (Repositório). Como há uma certa similaridade entre as ações das entidades, a ideia é ter um repositório genérico, que sirva como base para todas as entidades da aplicação.
por André Baltieri1. Introdução
O Repository Pattern é um padrão conhecido e que consiste em persistir o estado das entidades no banco de dados (Repositório).Como há uma certa similaridade entre as ações das entidades (Por exemplo os métodos CRUD), a ideia é ter um repositório genérico, que sirva como base para todas as entidades da aplicação.
Talvez alguns pontos possam ser melhorados, fiquem à vontade em sugerir melhorias.
2. Domain
Não vou explicar a criação do domínio para não perder tempo, mas é somente a criação das três classes abaixo:using System.ComponentModel.DataAnnotations; using System.Web.Mvc; namespace SampleApp.Domain.Entities { public class User { public int Id { get; set; } [Required(ErrorMessage="Campo obrigatório.")] [Display(Name = "Usuário")] public string UserName { get; set; } [Required(ErrorMessage = "Campo obrigatório.")] [DataType(DataType.EmailAddress)] [Display(Name = "E-mail")] public string Email { get; set; } [Required(ErrorMessage = "Campo obrigatório.")] [DataType(DataType.Password)] [Display(Name = "Senha")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirmar senha")] [Compare("Password", ErrorMessage = "As senhas digitadas não conferem.")] public string ConfirmPassword { get; set; } } }Classe User.cs
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace SampleApp.Domain.Entities { public class Role { public int Id { get; set; } [Required(ErrorMessage = "Campo obrigatório.")] [Display(Name = "Perfil")] public string Name { get; set; } [Required(ErrorMessage = "Campo obrigatório.")] [Display(Name = "Aplicação")] public int ApplicationId { get; set; } public virtual Application Application { get; set; } public virtual ICollection<User> Users { get; set; } } }Classe Role.cs
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace SampleApp.Domain.Entities { public class Application { public int Id { get; set; } [Required(ErrorMessage = "Campo obrigatório.")] [Display(Name = "Aplicação")] public string Name { get; set; } public virtual ICollection<Role> Roles { get; set; } } }Classe Application.cs
3. DataContext
O contexto é o responsável por gerenciar os estados das entidades e seus grupos. Desta forma, o contexto fica da seguinte maneira (Por hora):using System.Data.Entity; using SampleApp.Domain.Entities; namespace SampleApp.Data.Contexts { public class SampleDataContext : DbContext { public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<Application> Applications { get; set; } } }Data/Contexts/SampleDataContext.cs
4. Unit of Work
Se utilizarmos o contexto do jeito que ele está, quando precisarmos abrir duas ou mais requisições em um mesmo contexto, teremos problemas. Na aplicação web por exemplo, trabalharemos com o famoso “Session Per Request” onde abrimos uma sessão quando a página inicia e já fechamos a mesma quando a página termina de ser renderizada, mantendo a aplicação sempre desconectada do banco de dados.Para fazer este gerenciamento utilizaremos o Unit Of Work (Referência 1), que manterá a lista de objetos modificados, excluídos e incluídos (Tracker) na sessão. Com estes itens na sessão, nós decidiremos quando “comitar” estas modificações ou não.
Sua implementação é simples, bastando criar uma interface como na Listagem 1 e implementa-lá no contexto, como na Listagem 2.
namespace SampleApp.Data.Contexts.Interfaces { public interface IUnitOfWork { void Save(); } }Listagem 1 - Unit of Work
using System.Data.Entity; using SampleApp.Data.Contexts.Interfaces; using SampleApp.Domain.Entities; namespace SampleApp.Data.Contexts { public class SampleDataContext : DbContext, IUnitOfWork { public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<Application> Applications { get; set; } public void Save() { base.SaveChanges(); } } }Listagem 2 - Versão final do contexto
5. Repositório Genérico
O repositório genérico conterá os métodos comuns entre todos os repositórios da aplicação, fique à vontade em incluir outros métodos que julgue comum em sua aplicação na interface e classe.Deste modo, criaremos a interface determinando quais métodos serão expostos e uma classe que implementará esta interface.
using System.Linq; namespace SampleApp.Data.Repositories.Interfaces { public interface IBaseRepository<T> where T : class { T Find(int id); IQueryable<T> List(); void Add(T item); void Remove(T item); void Edit(T item); } }Data/Repositories/Interfaces/IBaseRepository.cs
using System; using System.Data; using System.Linq; using SampleApp.Data.Contexts; using SampleApp.Data.Contexts.Interfaces; using SampleApp.Data.Repositories.Interfaces; namespace SampleApp.Data.Repositories { public class BaseRepository<T> : IDisposable, IBaseRepository<T> where T : class { private SampleDataContext _context; #region Ctor public BaseRepository(IUnitOfWork unitOfWork) { if (unitOfWork == null) throw new ArgumentNullException("unitOfWork"); _context = unitOfWork as SampleDataContext; } #endregion public T Find(int id) { return _context.Set<T>().Find(id); } public IQueryable<T> List() { return _context.Set<T>(); } public void Add(T item) { _context.Set<T>().Add(item); } public void Remove(T item) { _context.Set<T>().Remove(item); } public void Edit(T item) { _context.Entry(item).State = EntityState.Modified; } public void Dispose() { _context.Dispose(); } } }Data/Repositories/BaseRepository.cs
A única observação é que o repositório recebe em seu construtor o “Unit of Work” que se refere a sessão atual onde as entidades serão gerenciadas.
6. Outros repositórios
Com o repositório genérico criado, vamos criar os outros repositórios, e para cada repositório sua interface, para que possam ser injetadas futuramente.Aqui listarei somente o repositório da classe Role, para ver o resto consulte o código disponibilizado na sessão 10 deste artigo.
using System; using SampleApp.Domain.Entities; namespace SampleApp.Data.Repositories.Interfaces { public interface IRoleRepository : IBaseRepository<Role>, IDisposable { } }Data/Repositories/Interfaces/IRoleRepository.cs
using SampleApp.Data.Contexts; using SampleApp.Data.Contexts.Interfaces; using SampleApp.Data.Repositories.Interfaces; using SampleApp.Domain.Entities; namespace SampleApp.Data.Repositories { public class RoleRepository: BaseRepository<Role>, IRoleRepository { IUnitOfWork unitOfWork = new SampleDataContext(); public RoleRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { } } }Data/Repositories/RoleRepository.cs
Como existem várias formas de gerenciar contexto com Unit of Work, aqui eu instancio ele na classe internamente, mas ele poderia ser recebido pelo construtor e passado adiante.
7. Camada de serviços
Com os repositórios criados, é hora de ver como utiliza-los, e para este caso utilizarei a camada de serviços.A camada de serviços acumula toda burocracia que ficaria nos controllers, e como a ideia é ter os controllers limpos, podemos trazer o código para cá para ajudar.
Assim como os repositórios tem suas operações em comum, os serviços também tem.
Nota: Para utilizar o ModelStateDictionary é preciso adicionar referência ao namespace System.Web.Mvc.
using System.Linq; namespace SampleApp.Service.Interfaces { public interface IBaseService<T> where T : class { T Find(int id); IQueryable<T> List(); void Add(T item); void Remove(T item); void Edit(T item); } }IBaseService.cs
using System.Linq; using SampleApp.Data.Contexts; using SampleApp.Data.Contexts.Interfaces; using SampleApp.Data.Repositories; using SampleApp.Data.Repositories.Interfaces; using SampleApp.Service.Interfaces; namespace SampleApp.Service { public class BaseService<T> : IBaseService<T> where T : class { IUnitOfWork unitOfWork = new SampleDataContext(); IBaseRepository<T> _repository; public BaseService() { _repository = new BaseRepository<T>(unitOfWork); } public T Find(int id) { return _repository.Find(id); } public IQueryable<T> List() { return _repository.List(); } public void Add(T item) { _repository.Add(item); unitOfWork.Save(); } public void Remove(T item) { _repository.Remove(item); unitOfWork.Save(); } public void Edit(T item) { _repository.Edit(item); unitOfWork.Save(); } public void Dispose() { _repository.Dispose(); } } }BaseService.cs
Com o serviço base criado, basta criar os outros serviços herdando deste serviço base e implementar os métodos adicionais necessários.
Nos exemplos abaixo, criei um método Validate(T item) para exemplificar a criação de métodos adicionais. Na maioria das vezes este método verifica se o item já está cadastrado.
using System; using SampleApp.Domain.Entities; namespace SampleApp.Service.Interfaces { public interface IRoleService : IBaseService<Role>, IDisposable { bool Validate(Role role); } }IRoleService.cs
using System.Linq; using System.Web.Mvc; using SampleApp.Domain.Entities; using SampleApp.Service.Interfaces; namespace SampleApp.Service { public class RoleService : BaseService<Role>, IRoleService { private ModelStateDictionary _modelState; public RoleService(ModelStateDictionary modelState) { _modelState = modelState; } public bool Validate(Role item) { if (item.Id == 0) { if (this.List().Where(c => c.Name == item.Name).Count() > 0) _modelState.AddModelError("Name", "Perfil já cadastrado"); } return _modelState.IsValid; } } }RoleService.cs
Os demais serviços podem ser vistos no código fonte, que pode ser baixado no fim do artigos.
8. Integrando com ASP.NET MVC
A integração com o ASP.NET MVC é tranquila, abaixo está o código do controller RoleController.cs. Escolhi este controller pois o Role pertence à uma Application, e sendo assim, será necessário ter um DropDownList para escolher a aplicação, na criação do perfil.Os outros controllers estão no source.
using System.Data.Entity; using System.Linq; using System.Web.Mvc; using SampleApp.Domain.Entities; using SampleApp.Service; using SampleApp.Service.Interfaces; namespace SampleApp.Web.Controllers { public class RoleController : Controller { IRoleService _service; IApplicationService _applicationService; public RoleController() { _service = new RoleService(this.ModelState); _applicationService = new ApplicationService(this.ModelState); } #region Actions public ActionResult Index() { return View( _service.List().Include(r => r.Application)); } public ViewResult Details(int id) { return View(_service.List() .Include(r => r.Application) .Where(r => r.Id == id).First()); } public ActionResult Create() { ViewBag.ApplicationId = new SelectList(_applicationService.List(), "Id", "Name"); return View(); } [HttpPost] public ActionResult Create(Role role) { if (_service.Validate(role)) { _service.Add(role); return RedirectToAction("Index"); } ViewBag.ApplicationId = new SelectList(_applicationService.List(), "Id", "Name"); return View(role); } public ActionResult Edit(int id) { var role = _service.Find(id); ViewBag.ApplicationId = new SelectList( _applicationService.List(), "Id", "Name", role.ApplicationId); return View(_service.Find(id)); } [HttpPost] public ActionResult Edit(Role role) { if (_service.Validate(role)) { _service.Edit(role); return RedirectToAction("Index"); } ViewBag.ApplicationId = new SelectList( _applicationService.List(), "Id", "Name", role.ApplicationId); return View(role); } #endregion protected override void Dispose(bool disposing) { _service.Dispose(); base.Dispose(disposing); } } }RoleController.cs
9. Conclusão
OK, eu concordo que escrevi um bocado de código, mas que isto irá salvar muitas linhas de código posteriormente.Generalizando os repositórios e os serviços, economizamos boa parte do tempo de desenvolvimento e teste, já que a maioria das entidades possuem métodos CRUD e para muitas os métodos CRUD são tudo que elas possuem.
10. Source
EF 4.1 Generic Repository http://files.insidedotnet.com.br/EF4.1.Generic.Repository.zip11. Referências
1. Using Repository and Unit of Work patterns with Entity Framework 4.0http://blogs.msdn.com/b/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx
2. ASP.NET MVC-Validação de dados [Camada de Serviço]
http://weblogs.asp.net/andrebaltieri/archive/2011/01/08/asp-net-mvc-valida-231-227-o-de-dados-camada-de-servi-231-o.aspx
- As edições 14 da Easy .net Magazine e 88 da .net Magazine já estão disponíveis.ADO.NET
- Postando no Twiiter com .NET e Migre.meC#
- Setup ApplicationsVisual Basic .NET
- Problemas na manipulação de arquivos do MS Excel com .NETVisual Basic .NET
- MP3 player com DirectXVisual Basic .NET