- Introdução
- Domain
- DataContexts
- Unit of Work
- Repositório Genérico
- Outros repositórios
- Camada de serviços
- Integrando com ASP.NET MVC
- Conclusão
- Source
- Referências
1. 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:
1: using System.ComponentModel.DataAnnotations;
2: using System.Web.Mvc;
3:
4: namespace SampleApp.Domain.Entities
5: {
6: public class User
7: {
8: public int Id { get; set; }
9:
10: [Required(ErrorMessage="Campo obrigatório.")]
11: [Display(Name = "Usuário")]
12: public string UserName { get; set; }
13:
14: [Required(ErrorMessage = "Campo obrigatório.")]
15: [DataType(DataType.EmailAddress)]
16: [Display(Name = "E-mail")]
17: public string Email { get; set; }
18:
19: [Required(ErrorMessage = "Campo obrigatório.")]
20: [DataType(DataType.Password)]
21: [Display(Name = "Senha")]
22: public string Password { get; set; }
23:
24: [DataType(DataType.Password)]
25: [Display(Name = "Confirmar senha")]
26: [Compare("Password", ErrorMessage = "As senhas digitadas não conferem.")]
27: public string ConfirmPassword { get; set; }
28: }
29: }
Classe User.cs
1: using System.Collections.Generic;
2: using System.ComponentModel.DataAnnotations;
3:
4: namespace SampleApp.Domain.Entities
5: {
6: public class Role
7: {
8: public int Id { get; set; }
9:
10: [Required(ErrorMessage = "Campo obrigatório.")]
11: [Display(Name = "Perfil")]
12: public string Name { get; set; }
13:
14: [Required(ErrorMessage = "Campo obrigatório.")]
15: [Display(Name = "Aplicação")]
16: public int ApplicationId { get; set; }
17: public virtual Application Application { get; set; }
18:
19: public virtual ICollection<User> Users { get; set; }
20: }
21: }
Classe Role.cs
1: using System.Collections.Generic;
2: using System.ComponentModel.DataAnnotations;
3:
4: namespace SampleApp.Domain.Entities
5: {
6: public class Application
7: {
8: public int Id { get; set; }
9:
10: [Required(ErrorMessage = "Campo obrigatório.")]
11: [Display(Name = "Aplicação")]
12: public string Name { get; set; }
13:
14: public virtual ICollection<Role> Roles { get; set; }
15: }
16: }
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):
1: using System.Data.Entity;
2: using SampleApp.Domain.Entities;
3:
4: namespace SampleApp.Data.Contexts
5: {
6: public class SampleDataContext : DbContext
7: {
8: public DbSet<User> Users { get; set; }
9: public DbSet<Role> Roles { get; set; }
10: public DbSet<Application> Applications { get; set; }
11: }
12: }
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.
1: namespace SampleApp.Data.Contexts.Interfaces
2: {
3: public interface IUnitOfWork
4: {
5: void Save();
6: }
7: }
Listagem 1 – Unit of Work
1: using System.Data.Entity;
2: using SampleApp.Data.Contexts.Interfaces;
3: using SampleApp.Domain.Entities;
4:
5: namespace SampleApp.Data.Contexts
6: {
7: public class SampleDataContext : DbContext, IUnitOfWork
8: {
9: public DbSet<User> Users { get; set; }
10: public DbSet<Role> Roles { get; set; }
11: public DbSet<Application> Applications { get; set; }
12:
13: public void Save()
14: {
15: base.SaveChanges();
16: }
17: }
18: }
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.
1: using System.Linq;
2:
3: namespace SampleApp.Data.Repositories.Interfaces
4: {
5: public interface IBaseRepository<T> where T : class
6: {
7: T Find(int id);
8: IQueryable<T> List();
9: void Add(T item);
10: void Remove(T item);
11: void Edit(T item);
12: }
13: }
Data/Repositories/Interfaces/IBaseRepository.cs
1: using System;
2: using System.Data;
3: using System.Linq;
4: using SampleApp.Data.Contexts;
5: using SampleApp.Data.Contexts.Interfaces;
6: using SampleApp.Data.Repositories.Interfaces;
7:
8: namespace SampleApp.Data.Repositories
9: {
10: public class BaseRepository<T>
11: : IDisposable, IBaseRepository<T> where T : class
12: {
13: private SampleDataContext _context;
14:
15: #region Ctor
16: public BaseRepository(IUnitOfWork unitOfWork)
17: {
18: if (unitOfWork == null)
19: throw new ArgumentNullException("unitOfWork");
20:
21: _context = unitOfWork as SampleDataContext;
22: }
23: #endregion
24:
25: public T Find(int id)
26: {
27: return _context.Set<T>().Find(id);
28: }
29:
30: public IQueryable<T> List()
31: {
32: return _context.Set<T>();
33: }
34:
35: public void Add(T item)
36: {
37: _context.Set<T>().Add(item);
38: }
39:
40: public void Remove(T item)
41: {
42: _context.Set<T>().Remove(item);
43: }
44:
45: public void Edit(T item)
46: {
47: _context.Entry(item).State = EntityState.Modified;
48: }
49:
50: public void Dispose()
51: {
52: _context.Dispose();
53: }
54: }
55: }
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.
1: using System;
2: using SampleApp.Domain.Entities;
3:
4: namespace SampleApp.Data.Repositories.Interfaces
5: {
6: public interface IRoleRepository : IBaseRepository<Role>, IDisposable
7: {
8: }
9: }
Data/Repositories/Interfaces/IRoleRepository.cs
1: using SampleApp.Data.Contexts;
2: using SampleApp.Data.Contexts.Interfaces;
3: using SampleApp.Data.Repositories.Interfaces;
4: using SampleApp.Domain.Entities;
5:
6: namespace SampleApp.Data.Repositories
7: {
8: public class RoleRepository: BaseRepository<Role>, IRoleRepository
9: {
10: IUnitOfWork unitOfWork = new SampleDataContext();
11:
12: public RoleRepository(IUnitOfWork unitOfWork)
13: : base(unitOfWork)
14: {
15:
16: }
17: }
18: }
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.
1: using System.Linq;
2:
3: namespace SampleApp.Service.Interfaces
4: {
5: public interface IBaseService<T> where T : class
6: {
7: T Find(int id);
8: IQueryable<T> List();
9: void Add(T item);
10: void Remove(T item);
11: void Edit(T item);
12: }
13: }
IBaseService.cs
1: using System.Linq;
2: using SampleApp.Data.Contexts;
3: using SampleApp.Data.Contexts.Interfaces;
4: using SampleApp.Data.Repositories;
5: using SampleApp.Data.Repositories.Interfaces;
6: using SampleApp.Service.Interfaces;
7:
8: namespace SampleApp.Service
9: {
10: public class BaseService<T> : IBaseService<T> where T : class
11: {
12: IUnitOfWork unitOfWork = new SampleDataContext();
13: IBaseRepository<T> _repository;
14:
15: public BaseService()
16: {
17: _repository = new BaseRepository<T>(unitOfWork);
18: }
19:
20: public T Find(int id)
21: {
22: return _repository.Find(id);
23: }
24:
25: public IQueryable<T> List()
26: {
27: return _repository.List();
28: }
29:
30: public void Add(T item)
31: {
32: _repository.Add(item);
33: unitOfWork.Save();
34: }
35:
36: public void Remove(T item)
37: {
38: _repository.Remove(item);
39: unitOfWork.Save();
40: }
41:
42: public void Edit(T item)
43: {
44: _repository.Edit(item);
45: unitOfWork.Save();
46: }
47:
48: public void Dispose()
49: {
50: _repository.Dispose();
51: }
52: }
53: }
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.
1: using System;
2: using SampleApp.Domain.Entities;
3:
4: namespace SampleApp.Service.Interfaces
5: {
6: public interface IRoleService : IBaseService<Role>, IDisposable
7: {
8: bool Validate(Role role);
9: }
10: }
IRoleService.cs
1: using System.Linq;
2: using System.Web.Mvc;
3: using SampleApp.Domain.Entities;
4: using SampleApp.Service.Interfaces;
5:
6: namespace SampleApp.Service
7: {
8: public class RoleService : BaseService<Role>, IRoleService
9: {
10: private ModelStateDictionary _modelState;
11:
12: public RoleService(ModelStateDictionary modelState)
13: {
14: _modelState = modelState;
15: }
16:
17: public bool Validate(Role item)
18: {
19: if (item.Id == 0)
20: {
21: if (this.List().Where(c => c.Name == item.Name).Count() > 0)
22: _modelState.AddModelError("Name", "Perfil já cadastrado");
23: }
24:
25: return _modelState.IsValid;
26: }
27: }
28: }
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.
1: using System.Data.Entity;
2: using System.Linq;
3: using System.Web.Mvc;
4: using SampleApp.Domain.Entities;
5: using SampleApp.Service;
6: using SampleApp.Service.Interfaces;
7:
8: namespace SampleApp.Web.Controllers
9: {
10: public class RoleController : Controller
11: {
12: IRoleService _service;
13: IApplicationService _applicationService;
14:
15: public RoleController()
16: {
17: _service = new RoleService(this.ModelState);
18: _applicationService = new ApplicationService(this.ModelState);
19: }
20:
21: #region Actions
22: public ActionResult Index()
23: {
24: return View(
25: _service.List().Include(r => r.Application));
26: }
27:
28: public ViewResult Details(int id)
29: {
30: return View(_service.List()
31: .Include(r => r.Application)
32: .Where(r => r.Id == id).First());
33: }
34:
35: public ActionResult Create()
36: {
37: ViewBag.ApplicationId =
38: new SelectList(_applicationService.List(), "Id", "Name");
39: return View();
40: }
41:
42: [HttpPost]
43: public ActionResult Create(Role role)
44: {
45: if (_service.Validate(role))
46: {
47: _service.Add(role);
48: return RedirectToAction("Index");
49: }
50:
51: ViewBag.ApplicationId =
52: new SelectList(_applicationService.List(), "Id", "Name");
53: return View(role);
54: }
55:
56: public ActionResult Edit(int id)
57: {
58: var role = _service.Find(id);
59: ViewBag.ApplicationId = new SelectList(
60: _applicationService.List(), "Id", "Name", role.ApplicationId);
61: return View(_service.Find(id));
62: }
63:
64: [HttpPost]
65: public ActionResult Edit(Role role)
66: {
67: if (_service.Validate(role))
68: {
69: _service.Edit(role);
70: return RedirectToAction("Index");
71: }
72:
73: ViewBag.ApplicationId = new SelectList(
74: _applicationService.List(), "Id", "Name", role.ApplicationId);
75: return View(role);
76: }
77: #endregion
78:
79: protected override void Dispose(bool disposing)
80: {
81: _service.Dispose();
82: base.Dispose(disposing);
83: }
84:
85: }
86: }
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
11. Referências
- Using Repository and Unit of Work patterns with Entity Framework 4.0
http://blogs.msdn.com/b/adonet/archive/2009/06/16/using-repository-and-unit-of-work-patterns-with-entity-framework-4-0.aspx
- 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