terça-feira, 1 de novembro de 2011

Repository Pattern

Utilizando o padrão repositório, as telas e páginas da aplicação que fazem acessos a banco de dados passam a acessar classes intermediárias que atuam como repositórios de dados, tornando a aplicação independente de um banco de dados real. Dessa forma, há uma melhor separação de responsabilidades, onde as telas não possuem lógica de persistência, que passa a ser de responsabilidade do repositório. Isso facilita a troca de banco de dados (utilizar SQL Server, Oracle, MySQL, etc) e também os testes, uma vez que podemos criar repositórios “fake”, trabalhando com listas em memória, além de separar lógica de persistência da lógica de interface com o usuário.

Unit Of Work Pattern

Recentemente refatorei a minha versão de repositório que utilizo em minhas aplicações para dar suporte ao padrão UnitOfWork. Utilizando esse padrão, os repositórios passam a compartilhar entre si um objeto que faz a coordenação das transações dos repositórios a ele anexados em uma única operação Commit/Rollback. Ou seja, ao instanciar os repositórios para uma determinada operação, devo instanciar antes um objeto que implementa IUnitOfWork, e passá-lo na construção desses repositórios. Ao chamar o método Commit() do UnitOfWork, todos os repositórios anexados serão persistidos.


Como foi implementado o repositório?

Após uma refatoração completa, a interface do repositório agora depende de uma interface IUnitOfWork:

using System;
using System.Linq;
namespace Metavision.Infra.Data
{
public interface IUnitOfWork : IDisposable
{
T Find<T>(params object[] keyValues) where T : class , new();
/// <summary>
/// Deve adicionar um registro ao repositório
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
void Add<T>(T entity) where T : class;
/// <summary>
/// Deve remover um registro do repositório
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
void Remove<T>(T entity) where T : class;
/// <summary>
/// Deve retornar um objeto IQueryable do repositório
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IQueryable<T> Get<T>() where T : class;
/// <summary>
/// Deve retornar a quantidade de repositórios que estão anexados ao UnitOfWork atual
/// </summary>
int AttachedRepositories { get; }
/// <summary>
/// Persiste os dados de todos os repositórios anexados ao UnitOfWork atual
/// </summary>
/// <returns></returns>
int Commit();
/// <summary>
/// Deve cancelar todas as operações pendentes
/// </summary>
void RollBack();
}
}
view raw IUnitOfWork.cs hosted with ❤ by GitHub


Com essa interface criada, podemos criar agora a interface IRepositorio:


using System;
using System.Collections.Generic;
namespace Metavision.Infra.Data
{
public interface IRepositorio : IDisposable
{
bool HasChanges { get; set; }
IUnitOfWork UnitOfWork { get; }
}
public interface IRepositorio<T> : IRepositorio where T : class, new()
{
Func<T> Factory { get; }
T CreateNew();
void AddEntity(T entity);
void RemoveEntity(T entity);
T GetEntityByKey(params object[] key);
T GetEntity(Func<T, Boolean> specification = null);
IEnumerable<T> GetAll(Func<T, bool> specification = null);
}
}
view raw IRepositorio.cs hosted with ❤ by GitHub


Uma classe de extensão para alguns métodos úteis aos repositórios que forem implementados:

using System;
using System.Collections.Generic;
using System.Linq;
using Metavision.Infra.Data;
namespace Metavision.Infra.Extensions
{
public static class DataExtensions
{
#region extensoes para Repositório
public static void RemoveMany<T>(this IRepositorio<T> repositorio, Func<T, bool> where) where T : class, new()
{
if (repositorio == null) return;
var results = repositorio.GetAll(where);
foreach (var result in results)
{
repositorio.RemoveEntity(result);
}
}
public static IQueryable<T> GetQueryable<T>(this IRepositorio<T> repositorio) where T : class, new()
{
return repositorio.GetAll().AsQueryable();
}
public static PagedResult<T> GetPagedResult<T>(this IQueryable<T> queryable, int size) where T : class, new()
{
return new PagedResult<T>(queryable, size);
}
internal static void AttachToUnitOfWork(this IRepositorio repositorio)
{
if (repositorio == null) return;
if (repositorio.UnitOfWork is UnitOfWorkBase)
{
((UnitOfWorkBase)repositorio.UnitOfWork).AttachRepository(repositorio);
}
}
#endregion
}
}

Temos o repositório, agora falta a implementação da interface UnitOfWork. Para implementar esta interface, achei conveniente criar uma classe base:
using System;
using System.Collections.Generic;
using System.Data;
namespace Metavision.Infra.Data
{
public abstract class UnitOfWorkBase : IDisposable
{
protected readonly Stack<IRepositorio> StackRepositories
= new Stack<IRepositorio>();
protected IDbTransaction CurrentTransaction;
protected readonly Dictionary<Type, object> Sets
= new Dictionary<Type, object>();
/// <summary>
/// Deve anexar um repositório
/// </summary>
/// <param name="repositorio"></param>
protected internal void AttachRepository(IRepositorio repositorio)
{
StackRepositories.Push(repositorio);
}
protected void SetHasChangesToFalse()
{
foreach (var rep in StackRepositories)
{
rep.HasChanges = false;
}
}
public int AttachedRepositories
{
get { return StackRepositories.Count; }
}
/// <summary>
/// Deve indicar se há uma transação aberta com o banco de dados
/// </summary>
public bool InTransaction
{
get
{
return
CurrentTransaction != null &&
CurrentTransaction.Connection != null &&
CurrentTransaction.Connection.State == ConnectionState.Open;
}
}
/// <summary>
/// Deve iniciar uma transação
/// </summary>
internal abstract void StartTransaction();
#region Implementation of IDisposable
public virtual void Dispose()
{
while (StackRepositories.Count > 0)
{
StackRepositories.Pop().Dispose();
}
if (InTransaction) CurrentTransaction.Dispose();
}
#endregion
}
}

Precisamos da classe Container para nos auxiliar nas implementações de UnitOfWork:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Metavision.Infra
{
/// <summary>
/// Service Locator simples
/// </summary>
public static class Container
{
#region Fields
/// <summary>
/// Assemblies que serão incluídos na pesquisa de tipos por string
/// </summary>
internal static readonly List<Assembly> Assemblies = new List<Assembly>();
/// <summary>
/// Dicionário que guarda instâncias singleton
/// </summary>
private static readonly Dictionary<Type, object> ActiveServices = new Dictionary<Type, object>();
/// <summary>
/// Dicionário que guarda um ponteiro para um método que fabrica um determinado tipo
/// </summary>
private static readonly Dictionary<Type, Func<object>> Factories = new Dictionary<Type, Func<object>>();
/// <summary>
/// objeto utilizado para lock de threads
/// </summary>
private static readonly object LockerObj = new object();
/// <summary>
/// Dicionário que guarda configurações para o Container
/// </summary>
private static readonly Dictionary<Type, Type> ServiceEntries = new Dictionary<Type, Type>();
/// <summary>
/// Dicionário que guarda tipos encontrados por string
/// </summary>
private static readonly Dictionary<string, Type> TypeDictionary = new Dictionary<string, Type>();
#endregion Fields
#if !SILVERLIGHT
static Container()
{
Assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies());
}
#endif
#region Methods
public static void Initialize()
{
}
public static void DisposeService<TService>()
{
if (ActiveServices.ContainsKey(typeof(TService)))
{
var disposable = ActiveServices[typeof(TService)] as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
ActiveServices[typeof(TService)] = null;
}
public static TServiceOut GetService<TService, TServiceOut>(bool singleton = true, object[] args = null)
where TServiceOut : class
{
return GetService(typeof(TService), singleton, args) as TServiceOut;
}
public static TService GetService<TService>(bool singleton = true, object[] args = null)
{
return (TService)GetService(typeof(TService), singleton, args);
}
public static object GetService(Type serviceType, bool singleton, object[] args = null)
{
//thread safety
lock (LockerObj)
{
if (serviceType == null) return null;
object service = null;
if (singleton && ActiveServices.ContainsKey(serviceType))
{
service = ActiveServices[serviceType];
if (service != null)
return service;
}
try
{
var typeToActivate = ServiceEntries.ContainsKey(serviceType)
? ServiceEntries[serviceType]
: serviceType;
if (!typeToActivate.IsInterface && !typeToActivate.IsAbstract)
{
service = Factories.ContainsKey(serviceType) ? Factories[serviceType]() : Activator.CreateInstance(typeToActivate, args);
}
else if (typeToActivate.IsGenericType)
{
var closedGeneric = ResolveOpenGeneric(typeToActivate);
if (closedGeneric != null)
{
service = Activator.CreateInstance(closedGeneric, args);
}
}
}
finally
{
if (singleton) ActiveServices[serviceType] = service;
}
return service;
}
}
public static void Register<TBase>(Func<TBase> factory = null)
where TBase : class
{
Register<TBase, TBase>(factory);
}
public static void Register<TBase, TConcrete>(Func<TBase> factory = null)
where TBase : class
where TConcrete : class
{
Register(typeof(TBase), typeof(TConcrete), factory as Func<object>);
}
public static void Register(Type tipoBase, Type tipoConcreto, Func<object> factory = null)
{
if (tipoBase == null) throw new ArgumentNullException("tipoBase");
if (tipoConcreto == null)
tipoConcreto = tipoBase;
ServiceEntries[tipoBase] = tipoConcreto;
ActiveServices[tipoBase] = null;
if (factory != null)
Factories[tipoBase] = factory;
}
public static TConcrete RegistraInstancia<TBase, TConcrete>(TConcrete instancia)
{
ServiceEntries[typeof(TBase)] = typeof(TConcrete);
ActiveServices[typeof(TBase)] = instancia;
return instancia;
}
public static TBase RegistraInstancia<TBase>(TBase instancia)
{
ServiceEntries[typeof(TBase)] = instancia.GetType();
ActiveServices[typeof(TBase)] = instancia;
return instancia;
}
public static void Reset()
{
ServiceEntries.Clear();
ActiveServices.Clear();
Factories.Clear();
TypeDictionary.Clear();
}
public static object TryResolve(string typeName, bool singleton)
{
var foundType = SearchType(typeName);
return foundType != null ? GetService(foundType, singleton) : null;
}
public static Type SearchType(string typeName)
{
Type foundType = null;
if (!string.IsNullOrEmpty(typeName))
{
if (TypeDictionary.ContainsKey(typeName))
{
return TypeDictionary[typeName];
}
foundType = Type.GetType(typeName, false) ?? SearchInAssemblies(typeName);
//registra mesmo que não tenha encontrado o tipo
//para da proxima vez não perder tempo
TypeDictionary[typeName] = foundType;
}
return foundType;
}
private static Type SearchInAssemblies(string typeName)
{
if (Assemblies != null)
{
foreach (var assembly in Assemblies)
{
var foundType = assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
if (foundType != null) return foundType;
}
}
return null;
}
private static Type ResolveOpenGeneric(Type type)
{
var newTypeToResolve = type.GetGenericTypeDefinition(); //openGeneric
var extractedType = type.GetGenericArguments(); //parameter
var objetoRegistrado = ServiceEntries.FirstOrDefault(o => o.Key == newTypeToResolve);
if (objetoRegistrado.Key != null)
{
var concreteType = objetoRegistrado.Value.MakeGenericType(extractedType);
return concreteType;
}
return null;
}
#endregion Methods
}
}
view raw Container.cs hosted with ❤ by GitHub


Em seguida todas as implementações para os diversos bancos de dados que desejarmos devem herdar desta classe base, implementando a interface. A seguir a implementação padrão que utiliza Entity Framework CodeFirst, onde no construtor passa-se o DbContext.
using System;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Objects;
using System.Linq;
namespace Metavision.Infra.Data
{
/// <summary>
/// Implementação de UnitOfWork para EntityFramework (CodeFirst)
/// Basicamente funciona abstraindo as operações do DbContext
/// Permitindo trabalhar com injeção de dependência
/// </summary>
public class CodeFirstUnitOfWork : UnitOfWorkBase, IUnitOfWork, IObjectContextAdapter
{
#region Members
private readonly DbContext _dbContext;
#endregion
#region Ctor
public CodeFirstUnitOfWork(DbContext dbContext = null)
{
_dbContext = dbContext ?? Container.GetService<DbContext>(false);
}
#endregion
#region Implementation of IObjectContextAdapter
public ObjectContext ObjectContext
{
get { return ((IObjectContextAdapter)_dbContext).ObjectContext; }
}
#endregion
#region Implementation of IUnitOfWork
public T Find<T>(params object[] keyValues) where T : class, new()
{
return GetDbSet<T>().Find(keyValues);
}
public void Add<T>(T entity) where T : class
{
GetDbSet<T>().Add(entity);
}
public void Remove<T>(T entity) where T : class
{
GetDbSet<T>().Remove(entity);
}
public IQueryable<T> Get<T>() where T : class
{
return GetDbSet<T>();
}
private DbSet<T> GetDbSet<T>() where T : class
{
DbSet<T> set;
if (Sets.ContainsKey(typeof(T)))
set = (DbSet<T>)Sets[typeof(T)];
else
{
set = _dbContext.Set<T>();
Sets[typeof(T)] = set;
}
return set;
}
internal override void StartTransaction()
{
if (!InTransaction)
{
ObjectContext.Connection.Close();
if (ObjectContext.Connection.State != ConnectionState.Open)
{
ObjectContext.Connection.Open();
CurrentTransaction = ObjectContext.Connection.BeginTransaction();
}
}
}
public int Commit()
{
var hasChanges = StackRepositories.Any(w => w.HasChanges);
if (!hasChanges) return 0;
try
{
StartTransaction();
var errors = _dbContext.GetValidationErrors();
var errorsCount = errors.Count();
if (errorsCount > 0)
{
RollBack();
return errorsCount * -1;
}
var count = _dbContext.SaveChanges();
CurrentTransaction.Commit();
SetHasChangesToFalse();
return count;
}
catch (Exception)
{
RollBack();
return -1;
}
}
public void RollBack()
{
SetHasChangesToFalse();
if (!InTransaction) return;
foreach (var dbEntityEntry in _dbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.State != EntityState.Added)
dbEntityEntry.Reload();
}
CurrentTransaction.Rollback();
}
#endregion
public override void Dispose()
{
base.Dispose();
_dbContext.Dispose();
}
}
}

A seguir a implementação padrão que utiliza Entity Framework tradicional, onde no construtor passa-se o ObjectContext.



Solução

Download da solution Abaixo segue o código demonstrando como utilizar o Entity Framework 4.1 Code First com 2 tabelas (Pessoas e Enderecos), com relacionamento Many-to-Many, e abstraindo o EFramework com o padrão repositório da forma mais simples, sem injeção de dependência.

Mostre código! 

Classe Pessoa


Classe Endereco


Classe de Mapeamento e Configuração do Entity Framework


Método de teste

Um comentário:

  1. Jone sua classe contempla consulta com uso do Include? Por exemplo:

    Dim x = (From a In New Context().CLIENTES.Include("PEDIDOS") Where a.ID = 1).FirstOrDefault

    ResponderExcluir