.NET

6 mar, 2015

EF 6 – Entendendo o código do repositório – Parte 04

Publicidade

Neste artigo vamos continuar o nosso exemplo anterior, explicando em detalhes a implementação da interface IRepositorio.

Entendendo o código usado na implementação do repositório

No artigo anterior definimos a interface do nosso repositório e fizemos sua implementação na classe Repositorio.

O que é uma interface? Uma interface, no paradigma da orientação a objetos, é um tipo de classe que contém apenas as assinaturas de métodos, propriedades, eventos e indexadores.

A implementação dos membros é feita por uma classe concreta ou struct que implementa a interface.

Segue abaixo novamente o código da classe Repositorio, implementando a interface IRepositorio e a interface IDisposable:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace DAL
{
    public class Repositorio<T> : IRepositorio<T> , IDisposable where T : class
    {
        private CadastroEntities Context;

        protected Repositorio()
        {
            Context = new CadastroEntities();
        }

        public IQueryable<T> GetTodos()
        {
            return Context.Set<T>();
        }

        public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
        {
            return  return Context.Set<T>().Where(predicate);
        }

        public T Find(params object[] key)
        {
            return Context.Set<T>().Find(key);
        }

        public T First(Expression<Func<T, bool>> predicate)
        {
            return Context.Set<T>().Where(predicate).FirstOrDefault();
        }

        public void Adicionar(T entity)
        {
            Context.Set<T>().Add(entity);
        }

        public void Atualizar(T entity)
        {
            Context.Entry(entity).State = EntityState.Modified;
        }

        public void Deletar(Func<T, bool> predicate)
        {
            Context.Set<T>()
           .Where(predicate).ToList()
           .ForEach(del => Context.Set<T>().Remove(del));
        }

        public void Commit()
        {
            Context.SaveChanges();
        }

        public void Dispose()
        {
            if (Context != null)
            {
                Context.Dispose();
            }
                GC.SuppressFinalize(this);
            }
        }
}

Agora vamos detalhar e entender cada método implementado.

private CadastroEntities Context;
protected Repositorio()
{
     Context = new CadastroEntities();
}

Aqui estamos referenciando o contexto representando por CadastroEntities na variável Context e criando uma nova instância do contexto. Tudo depende do contexto e vamos usar sua referência em todos os métodos para acessar as entidades no Entity Data Model.

public IQueryable<T> GetTodos()
{
       return Context.Set<T>();
}

O método GetTodos() recebe uma entidade (uma classe) e retorna um IQueryable, ou seja uma lista completa das entidades (aqui o método Set<T> do contexto retorna uma instância instância DbSet<T> para o acesso a entidades de determinado tipo no contexto).

Obs.: para entender melhor o IQueryable veja o meu artigo:  .NET – Comparando IEnumerable com IQueryable.

public IQueryable<T> Get(Expression<Func<T, bool>> predicate)
{
    return  return Context.Set<T>().Where(predicate);
}

O método Get() usa um delegate Func<> como parâmetro de entrada, onde será usada uma expressão lambda (ex: p => p.EmpregadoId == ID) como critério e um predicate para validar o critério usando a cláusula Where. O retorno será uma lista IQueryable.

Obs.: para entender melhor o delegate Func<>, veja o meu artigo: C# – Apresentando o delegate Func

public T Find(params object[] key)
{
    return Context.Set<T>().Find(key);
}

O método Find() realiza uma busca pela chave primária. O parâmetro de entrada é um array de objetos. O método localiza uma entidade com os valores indicados na chave primária.

Se a entidade existir no contexto, então ela é devolvida imediatamente. Caso contrário, é feita uma solicitação à base. Se nenhuma entidade for encontrada no contexto ou na base, então é retornado null.

public T First(Expression<Func<T, bool>> predicate)
{
    return Context.Set<T>().Where(predicate).FirstOrDefault();
}

O método First() usa uma expressão com o delegate Func<> como entrada, aqui usaremos uma expressão lambda que será validada pelo predicate usando a cláusulaWhere. FirstOrDefault() garante que será retornado o primeiro elemento da sequência que satisfaça a condição definida na expressão lambda ou o valor padrão se nenhum elemento for encontrado.

public void Adicionar(T entity)
{
      Context.Set<T>().Add(entity);
}

O método Adicionar() recebe uma entidade e usando o método Add() do contexto inclui a entidade no contexto. Nos bastidores, o EF cria uma instrução SQL Insert.

Você pode incluir diversas entidades de uma vez. Apenas lembre-se que neste cenário estamos trabalhando na memória. Para persistir no banco de dados devemos o método SaveChanges().

public void Atualizar(T entity)
{
    Context.Entry(entity).State = EntityState.Modified;
}

O método Atualizar() recebe uma entidade e define o seu EntityState como Modified informando ao contexto que a entidade foi alterada.

O estado da entidade é uma enumeração do tipo System.Data.EntityState que declara os seguintes valores:

  1. Added – A entidade é marcada como adicionada;
  2. Deleted – A entidade é marcada como deletada;
  3. Modified – A entidade foi modificada;
  4. Unchanged – A entidade não foi modificada;
  5. Detached – A entidade não esta sendo tratada no contexto.

O contexto não só trata a referência para todos os objetos recuperados do banco de dados, mas também detém os estados da entidade e mantém as modificações feitas nas propriedades da entidade. Este recurso é conhecido como controle de alterações ou Change Tracking.

A mudança no estado da entidade de Unchanged para o estado Modified é o único estado que é feito automaticamente pelo contexto.

public void Deletar(Func<T, bool> predicate)
{
     Context.Set<T>()
    .Where(predicate).ToList()
    .ForEach(del => Context.Set<T>().Remove(del));
}

O método Deletar() usa como parâmetro de entrada um delegate Func<>, onde será usada uma expressão lambda, que será validada pelo predicate usando a cláusulaWhere(). Note que estamos usando o método Remove do contexto com uma expressão lambda (del => Context.Set<T>().Remove(del)) em um .ForEach() – o efeito disso é que ele irá varrer a lista retornada e remover todos os itens que atendem ao critério definido para remoção.

public void Commit()
{
       Context.SaveChanges();
}

O método Commit() é um dos mais importantes porque ele usa o método SaveChanges() do contexto para persistir no banco de dados as inclusões, exclusões e alterações feitas no contexto que ainda estão na memória.

Quando você trabalha com o Contexto e seus métodos, está trabalhando na memória. O método SaveChanges() efetiva todas as operações gravando-as no banco de dados. Assim, quando você trabalha na memória pode ter muitas entidades em diversos estados, e quando usa o SaveChanges() elas serão efetivamente gravadas no banco de dados.

public void Dispose()
{
     if (Context != null)
     {
          Context.Dispose();
      }
      GC.SuppressFinalize(this);
}

O método Dispose() implementa a interface IDisposable e verifica se o contexto não é nulo para liberar recursos usados

Os conceitos sobre Delegates (Func, Action, Predicate) e Expressões Lambdas são a base para entender o código usado na implementação feita.

Na próxima parte do artigo, vamos definir a nossa camada de negócios.