Want create site? Find Free WordPress Themes and plugins.

Hoje venho demonstrar como podemos implementar o design pattern Repository e Unit Of Work. Esses são alguns dos padrões mais comuns utilizados na construção de um software. A implementação desses padrões ajudam a isolar a camada de negócio da camada de armazenamento de dados e facilita de forma significativa a implementações de Unit Tests ou – Test Driven Development (TDD) .

O Design Pattern Repository e o Design Pattern Unit Of Work

O padrão Repository é responsável por intermediação entre as camadas de domínio e mapeamento de dados, agindo como uma coleção objeto de domínio abstraindo a implementação de acesso a banco de dados. Conceitualmente a classe que implementa o padrão Repository encapsula o conjunto de objetos e as operações executadas sobre eles , proporcionando uma visão mais orientada a objeto da camada de acesso a dados.

O padrão Unit of Work controla o que fazemos durante uma transação geralmente executada sobre uma regra de negócio e coordenando essas alterações para a camada de acesso a dados.

Agora que sabemos os que esses padrões representam a pergunta é como implementar? Vamos demonstrar esses padrões utilizando o Entity Framework.

Para esse tutorial, estarei utilizando o Entity Framework 6 Code First

Criando um Contexto Genérico Base

Para demonstrar esses padrões, vamos criar um contexto e o que seria o contexto ? O Contexto dentro do Entity Framework representa a abstração de escrita e leitura em um banco de dados. O Entity Framework utiliza o DbContext para fazer o “meio de campo” entre seus objetos mapeados e o banco de dados em si.

A principal ideia aqui é criar um contexto que seja fácil de ser utilizado em qualquer lugar e em qualquer projeto. Assim poderemos utilizar ao máximo do poder da orientação a objeto. Vamos criar uma classe chamada BaseContext.cs e adicione o código abaixo:

public class BaseContext<T> : DbContext where T : class
{
   public DbSet<T> DbSet
   {
       get;
       set;
   }
   public BaseContext(): base("EFConnectionString")
   {
      //Caso a base de dados não tenha sido criada, 
      //ao iniciar a aplicação iremos criar
      Database.SetInitializer<BaseContext<T>>(null);
   }

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {

      //Neste momento não iremos fazer nada, 
      //iremos voltar mais para frente para criar nosso mapeamos dinamicos
      base.OnModelCreating(modelBuilder);
   }

   public virtual void ChangeObjectState(object model, EntityState state)
   {
        //Aqui trocamos o estado do objeto, 
        //facilita quando temos alterações e exclusões
        ((IObjectContextAdapter)this)
                      .ObjectContext
                      .ObjectStateManager
                      .ChangeObjectState(model, state);
   }
}

Agora estamos com nossa classe base de contexto criada, devemos criar nossos objetos de domínio.

Criando os Objetos de Domínio

Neste tutorial irei criar somente dois objetos de domínio Usuário e Receita. Vamos ao código.

public class User
{  
   public Guid Id { get; set; }
   public String Nome { get; set; }
   public String Email { get; set; }
   public DateTime DtNascimento { get; set; } 
   public virtual IList<Recipe> Recipes { get; set; }
}
public class Recipe
{
   public Guid Id { get; set; }
   public String Title { get; set; }
   public String Ingredients { get; set; }
   public String Steps { get; set; }
   public Guid UserId { get; set; }
}

Mapeando os objetos com o EntityTypeConfiguration

Com nosso objetos de domínio criados devemos agora criar nossos mapeamentos. Os mapeamentos servem para dizer ao Entity Framework como que ele deve gravar e ler os dados no banco de dados. Tenho uma dica muito importante eu sempre crio uma Interface chamada IMapping, essa interface servirá para fazer nosso mapeamento dinâmicos que iremos criar mais para a frente.

Vamos criar nossos mapeamentos e a interface, conforme código abaixo:

public interface IMapping
{

}
public class UserMapping : EntityTypeConfiguration<User>, IMapping
{
   public UserMapping()
   {
      this.ToTable("User");
      this.HasKey(x => x.Id);
      this.Property(x => x.Id);
      this.Property(x => x.Nome).IsRequired().HasMaxLength(255);
      this.Property(x => x.Email).IsRequired().HasMaxLength(255);
      this.Property(x => x.DtNascimento).IsRequired();
      this.HasMany(x => x.Recipes).WithOptional().HasForeignKey(x => x.UserId);
   }
}
public class RecipeMapping : EntityTypeConfiguration<Recipe>, IMapping
{
  public RecipeMapping()
  {
      this.ToTable("Recipe");
      this.HasKey(x => x.Id);
      this.Property(x => x.Id);
      this.Property(x => x.Ingredients).HasMaxLength(1024);
      this.Property(x => x.Steps).HasMaxLength(1024);
      this.Property(x => x.Title).IsRequired().HasMaxLength(150);
   }

}

Carregando os Mapeamentos Dinamicamente

Com nossos mapeamentos criados, devemos agora carregá-los no Entity Framework. Esses mapeamentos serão carregados no evento do contexto chamando OnModelCreating e qual o grande problema? Devemos carregá-los um a um conforme exemplo abaixo.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{

   modelBuilder.Configurations.Add(new UserMapping());
   modelBuilder.Configurations.Add(new RecipeMapping());

   base.OnModelCreating(modelBuilder);
}

O evento acima está implementado na classe BaseContext.cs que acabamos de criar, porém isso não é muito inteligente concorda ? Se nosso banco tiver 30 tabelas vamos fazer isso na mão ? Se fosse 50 tabelas ?

Para resolver essa situação que iremos criar o mapeamento dinâmico, a principal ideia é caso seja criado um novo mapeamento não será necessário alterar o evento OnModelCreating, ele será capaz de identificar um novo modelo e carregá-lo dinamicamente. Parece complicado não acham porém e bem simples.

Vamos ao código:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{

    //Fazendo o mapeamento com o banco de dados
    //Pega todas as classes que estão implementando a interface IMapping
    //Assim o Entity Framework é capaz de carregar os mapeamentos
    var typesToMapping = (from x in Assembly.GetExecutingAssembly().GetTypes()
                          where x.IsClass && typeof(IMapping).IsAssignableFrom(x)
                          select x).ToList();

    // Varrendo todos os tipos que são mapeamento 
    // Com ajuda do Reflection criamos as instancias 
    // e adicionamos no Entity Framework
    foreach (var mapping in typesToMapping)
    {
        dynamic mappingClass = Activator.CreateInstance(mapping);
        modelBuilder.Configurations.Add(mappingClass);
    }
}

Agora com a implementação acima nosso Repositório é capaz de identificar um novo mapeamento e carregá-lo em tempo de execução.

Nosso contexto base está quase pronto, devemos implementar as operações de banco ou seja implementar o Unit Of Work Pattern

Com nosso contexto base quase pronto devemos adicionar as operações de manipulação de objeto. Vamos criar uma interface chamada IUnitOfWork essa interface será a responsável pelas as operações de banco de dados.

public interface IUnitOfWork<T> where T : class
{
  int Save(T model);
  int Update(T model);
  void Delete(T model);
  IEnumerable<T> GetAll();
  T GetById(object id);
  IEnumerable<T> Where(Expression<Func<T, bool>> expression);
  IEnumerable<T> OrderBy(Expression<System.Func<T, bool>> expression);
}

Nossa interface tem todas as operações para manipular um objeto e consultar um objeto a partir de uma fonte de dados. Agora devemos implementar essa interface, e claro, essa interface será implementada pela nossa classe base de contexto e graças ao poder da orientação a objetos todos os repositórios que herdarem da base terão essas operações.

Adicione o código abaixo na BaseContext.cs:

  public virtual int Save(T model)
  {
    this.DbSet.Add(model);
    return this.SaveChanges();
  }

  public virtual int Update(T model)
  {
    var entry = this.Entry(model);
    if (entry.State == EntityState.Detached)
        this.DbSet.Attach(model);

    this.ChangeObjectState(model, EntityState.Modified);
    return this.SaveChanges();
  }

  public virtual void Delete(T model)
  {
    var entry = this.Entry(model);
    if (entry.State == EntityState.Detached)
       this.DbSet.Attach(model);

    this.ChangeObjectState(model, EntityState.Deleted);
    this.SaveChanges();
  }

  public virtual IEnumerable<T> GetAll()
  {
     return this.DbSet.ToList();
  } 

  public virtual T GetById(object id)
  {
     return this.DbSet.Find(id);
  }

  public virtual IEnumerable<T> Where(Expression<Func<T, bool>> expression)
  {
     return this.DbSet.Where(expression);
  }

  public IEnumerable<T> OrderBy(Expression<Func<T, bool>> expression)
  {  
     return this.DbSet.OrderBy(expression);
  }

Nosso contexto base está implementado e para finalizar devemos criar nosso Repositório de Usuário e o Repositório de Receitas. Veja como fica a implementação deles utilizando os padrões.

public class UserRepository : BaseContext<User>, IUnitOfWork<User>
{

}

public class RecipeRepository : BaseContext<Recipe>, IUnitOfWork<Recipe>
{

}

Ficou muito simples não é mesmo ? Com o poder da orientação temos todos os recursos para criar um acesso ao banco padronizado.

Um exemplo de como poderíamos usar nosso repositório em um Controller MVC

 

public class UserController : Controller
{
   IUnitOfWork<User> UnitOfWorkUser { get; set; }

   public UserController()
   {
      this.UnitOfWorkUser = new UserRepository();
   }
        
   // GET: User
   public ActionResult Index()
   {
      return View();
   }

   // GET: User/Details/5
   public ActionResult Details(Guid id)
   {
       var model = this.UnitOfWorkUser.GetById(id);
       return View(model);
   }

   // GET: User/Create
   public ActionResult Create()
   {

       return View();
   }

   // POST: User/Create
   [HttpPost]
    public ActionResult Create(User model)
    {
        try
        {
           this.UnitOfWorkUser.Save(model);
           return RedirectToAction("Index");
        }
        catch
        {
           return View();
        }
    }

    // GET: User/Edit/5
    public ActionResult Edit(Guid id)
    {
        var model = this.UnitOfWorkUser.GetById(id);
        return View();
    }

    // POST: User/Edit/5
    [HttpPost]
    public ActionResult Edit(Guid id, User model)
    {
        try
        {
            model.Id = id;
            this.UnitOfWorkUser.Update(model);
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

    // POST: User/Delete/5
    [HttpPost]
    public ActionResult Delete(Guid id)
    {
       try
       {
           // TODO: Add delete logic here
           var model = this.UnitOfWorkUser.Where(x => x.Id == id).FirstOrDefault();
           this.UnitOfWorkUser.Delete(model);
           return RedirectToAction("Index");
        }
        catch
        {
           return View();
        }
    }
}

 

Chegamos ao final desse post, espero que ele possa ajudar a vocês a criarem um padrão de acesso a dados. Com a implementação acima não estamos limitados ao Entity Framework e podemos substituir facilmente por outro ORM como NHibernate e utilizar os mesmo conceitos.

O código está disponivel no GitHub através desse link

Abs e até a próxima.

Rafael Cruz

É desenvolvedor .NET há mais de 12 anos, certificado Microsoft desde de 2006, instrutor oficial Microsoft há 5 anos, Pós Graduado em Engenharia de Software pela UFRJ, suas certificações são Microsoft Certified Trainer, Microsoft Certified Solution Developer (Web, Application Lifecycle Management), Microsoft Certified Professional Developer, Microsoft Certified Tecnology Specialist.
Atualmente trabalha como arquiteto de sistema, escreve artigos no .NET Coders e no seu blog rafaelcruz.azurewebsites.net para compartilhar experiências na área de desenvolvimento Web, Aplicativos Móveis e Cloud Solutions.

Twitter LinkedIn 

Did you find apk for android? You can find new Free Android Games and apps.

Comentários

comentarios