Repositório Genérico com EF 4.1

  1. Introdução
  2. Domain
  3. DataContexts
  4. Unit of Work
  5. Repositório Genérico
  6. Outros repositórios
  7. Camada de serviços
  8. Integrando com ASP.NET MVC
  9. Conclusão
  10. Source
  11. 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

  1. 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
  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
Published Sunday, August 07, 2011 7:49 PM by andrebaltieri
Filed under: ,

Comments

# re: Repositório Genérico com EF 4.1

Tuesday, September 06, 2011 1:58 PM by Tarcisio

André tudo bem?

Cara se eu tiver duas bases de dados diferente, eu ainda preciso continuar trabalhando dois DataContext? um para cada base?

E da pra melhorar a iteração entre tabelas de bases diferentes com isso?

Gostei muito do seu exemplo cara.

Leave a Comment

(required) 
(required) 
(optional)
(required)