Unit of Work in Repository Pattern
Prova de conceito das Design Pattern de persistência de dados Unit of Work e Repository com Entity Framework Code-First Migrations no Visual Studio 2017
Criar a solução
Criar nova solução com três camadas orientadas ao domínio:- Pasta Application
- Pasta Domain
- Pasta Infrastructure
Code-First Migrations
Modelo e BD Iniciais
À Infrastructure adiciona-se o projecto do tipo biblioteca de nome EntityFramework com os repositórios de acesso à Entity Framework- Pasta Repositories
Na Consola de Gestão de Pacotes NuGet procede-se à instalação da Entity Framework, tendo este projecto como padrão
PM> install-package EntityFramework
Uma vez instalada a última versão da Entity Framework, adiciona-se ao projecto o DBContext utilizando uma classe ADO.NET Entity Data Model com Empty Code First Model a que se dá o nome "PlutoContext".
Ao Domain adiciona-se o projecto do tipo biblioteca com o Core:
- Pasta Model
O Model comporta as entidades de domínio. Exemplo: Author
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
, Course public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Level { get; set; }
public float FullPrice { get; set; }
public bool IsBeginnerCourse
{
get { return Level == 1; }
}
public int AuthorId { get; set; }
public virtual Author Author { get; set; }
}
.Uma vez criadas as entidades que definem o modelo de domínio e referenciado o projecto Core pela biblioteca de acesso a dados EntityFramework, define-se o contexto EF Code First na PlutoContext.cs:
using System.Data.Entity;
using UnitOfWorkRepositoryPattern.PoC.Core.Model;
namespace UnitOfWorkRepositoryPattern.PoC.EntityFramework
{
public class PlutoContext: DbContext
{
public PlutoContext()
: base("name=PlutoContext")
{
this.Configuration.LazyLoadingEnabled = false;
}
public virtual DbSet<author> Authors { get; set; }
public virtual DbSet<course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
Adiciona-se ao App.config a connectionstring PlutoContext à base de dados Pluto_Queries. Confirma-se o nome do servidor da data source no Server Explorer: o servidor local é .\MSSQLLocalDB.
<connectionStrings>
<add name="PlutoContext" connectionString="data source=(localdb)\MSSQLLocalDB;initial catalog=Pluto_Queries;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
Compila-se a solução e é criada a base de dados a partir do DbContext.
Activação das Code-First Migrations
Na Consola de Gestão de Pacotes NuGet procede-se à activação das migrações, tendo este projecto como padrãoPM> enable-migrations
A execução deste comando dá origem à pasta Migrations com a configuração e a migração inicial.
Geração e Execução das Code-First Migrations
O comando Add-Migration executa o scaffold da próxima migração Code-First. As alterações ao modelo dão origem a nova migração e vão ser registadas na tabela de histórico '__MigrationHistory'.Devem ser designadas para melhor identificação.
PM> add-migration <designação>
O comando Update-Database aplica a migração à base de dados.
PM> update-database –verbose
Repository Pattern
À biblioteca Core, adiciona-se a pasta Interfaces. Esta pasta comporta as interfaces intrínsecas aos repositórios e estendem uma interface de repositório genérico. Este repositório deve ser tratado como uma System.Collections.Generic com os métodos:- Add(object)
- Remove(object)
- Get(id)
- GetAll()
- Find(predicate)
public interface IRepository<TEntity> where TEntity : class
{
void Add(TEntity entitity);
void Remove(TEntity entity);
TEntity Get(int id);
IEnumerable<TEntity> GetAll();
IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
}
Exemplo de interfaces para os repositórios AuthorRepository e CourseRepository respectivamente:
IAuthorRepository
public interface IAuthorRepository: IRepository<Author>
{
Author GetAuthorWithCourses(int id);
}
, ICourseRepository
public interface ICourseRepository: IRepository<Course>
{
IEnumerable<Course> GetTopSellingCourses(int count);
IEnumerable<Course> GetCoursesWithAuthors(int pageIndex, int pageSize);
}
.À pasta Repositories da biblioteca EntityFramework, juntam-se as classes que implementam as interfaces definidas no Domain, uma por entidade: RepositoryBase e as classes que herdam desta: AuthorRepository e CourseRepository.
A classe base tem um DbContext genérico aplicável a qualquer aplicação:
public class RepositoryBase<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
public RepositoryBase(DbContext dbContext)
{
Context = dbContext;
}
public void Add(TEntity entitity)
{
Context.Set<TEntity>().Add(entitity);
}
public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
return Context.Set<TEntity>().Where(predicate);
}
public TEntity Get(int id)
{
return Context.Set<TEntity>().Find(id);
}
public IEnumerable<TEntity> GetAll()
{
return Context.Set<TEntity>().ToList();
}
public void Remove(TEntity entity)
{
Context.Set<TEntity>().Remove(entity);
}
}
Enquanto as classes descendentes desta que implementam a interface definida no Domain têm um PlutoContext. Exemplo:
public class AuthorRepository : RepositoryBase<Author>, IAuthorRepository
{
public PlutoContext PlutoContext
{
get { return Context as PlutoContext; }
}
public AuthorRepository(PlutoContext plutoContext) : base(plutoContext){ }
public Author GetAuthorWithCourses(int id)
{
return PlutoContext.Authors.Include(a => a.Courses).SingleOrDefault(a => a.Id == id);
}
}
Unit Of Work
A Unit Of Work é específica para O/RM. De acordo com Martin Fowler, a unidade de trabalhomantém uma lista de objectos afectados por uma transacção e coordena a escrita de mudanças e trata possíveis problemas de concorrência
A classe ObjectContext no Entity Framework é exemplo de uma Unidade de Trabalhoin Unit of work - O padrão de unidade de trabalho (.NET)
À biblioteca Core adiciona-se a interface IUnitOfWork que define o contrato para articulação dos repositórios
public interface IUnitOfWork: IDisposable
{
IAuthorRepository Authors { get; }
ICourseRepository Courses { get; }
int Complete();
}
À biblioteca EntityFramework adiciona-se a classe UnitOfWork, implementação da interface IUnitOfWork sobre os repositórios do PlutoContext.
public class UnitOfWork : IUnitOfWork
{
protected readonly PlutoContext _plutoContext;
public UnitOfWork(PlutoContext plutoContext)
{
_plutoContext = plutoContext;
Authors = new AuthorRepository(_plutoContext);
Courses = new CourseRepository(_plutoContext);
}
public IAuthorRepository Authors { get; private set; }
public ICourseRepository Courses { get; private set; }
public int Complete()
{
return _plutoContext.SaveChanges();
}
public void Dispose()
{
_plutoContext.Dispose();
}
}
Utilização
À pasta Application adiciona-se o projecto do tipo Console com o nome "Console.Application".
Na Consola de Gestão de Pacotes NuGet procede-se à instalação da Entity Framework, tendo este projecto como padrão
PM> install-package EntityFramework
Adiciona-se ao App.config a connectionstring definida na biblioteca EntityFramework da Infrastructure.
Referencia-se os projectos Core e EntityFramework.
Adiciona-se ao programa o código de orquestração das entidades de domínio e interfaces dos repositórios para desencadear as operações sobre o PlutoContext através da unidade de trabalho.
using UnitOfWorkRepositoryPattern.PoC.Core;
using UnitOfWorkRepositoryPattern.PoC.Core.Model;
using UnitOfWorkRepositoryPattern.PoC.EntityFramework;
IUnitOfWork unitOfWork = new UnitOfWork(new PlutoContext());
unitOfWork.Authors.Add(
new Author {
Id = 1,
Name = "Silvia Pinhao Lopes",
Courses = new List<Course>
{
new Course
{
AuthorId = 1,
Description = "First Course",
FullPrice = 12,
Id = 1,
Level = 1,
Name = "1st Course"
}
}
});
unitOfWork.Complete();
var allAuthors = unitOfWork.Authors.GetAll();
foreach (var author in allAuthors)
{
System.Console.WriteLine(author.Name);
unitOfWork.Authors.Remove(author);
}
unitOfWork.Complete();
unitOfWork.Dispose();
System.Console.Read();
Referências: Entity Framework Code First Migrations, Code-based Migration, Repository Pattern with C# and Entity Framework, Done Right
Licença CC BY-SA 4.0
Silvia Pinhão Lopes, 14.9.17
Sem comentários: