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:
  1. Pasta Application
  2. Pasta Domain
  3. 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ão
PM> 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 trabalho
manté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 Trabalho
in 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
Print Friendly and PDF

Sem comentários:

Com tecnologia do Blogger.