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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
Com essa interface criada, podemos criar agora a interface IRepositorio:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Uma classe de extensão para alguns métodos úteis aos repositórios que forem implementados:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
Jone sua classe contempla consulta com uso do Include? Por exemplo:
ResponderExcluirDim x = (From a In New Context().CLIENTES.Include("PEDIDOS") Where a.ID = 1).FirstOrDefault