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.cs3. 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.cs4. 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 contexto5. 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.csA ú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.csComo 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.csCom 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.csOs 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.cs9. 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


