Finalmente, vamos agora para a implementação do pattern Repository usando o Entity Framework. Na Parte 2 nós criamos a interface IRepository e também o ContextManager, um helper para a criação do contexto. A implementação será chamada EFRepository e definirá, através de generics, dois tipos, sendo que um será o tipo do objeto que será persistido e o outro será o tipo (classe) do contexto do Entity Framework. Vamos para o código!

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Data;
   4: using System.Data.Metadata.Edm;
   5: using System.Data.Objects;
   6: using System.Data.Objects.DataClasses;
   7: using System.Linq;
   8: using System.Linq.Expressions;
   9: using Net.Bragil.Data.Context;
  10:  
  11: namespace Net.Bragil.Data.Repository
  12: {
  13:     /// <summary>
  14:     /// Repositório genérico para o Entity Framework com as operações mais comuns de persistência, como inserção, atualização,
  15:     /// exclusão e seleção.
  16:     /// </summary>
  17:     /// <typeparam name="E">Tipo do objeto a ser persistido</typeparam>
  18:     /// <typeparam name="C">Tipo do contexto</typeparam>
  19:     public class EFRepository<E, C> : IRepository<E, C>, IDisposable
  20:         where E : EntityObject
  21:         where C : ObjectContext
  22:     {
  23:         /// <summary>
  24:         /// Contexto do Entity Framework
  25:         /// </summary>
  26:         private C context;
  27:  
  28:         /// <summary>
  29:         /// Contexto do Entity Framework
  30:         /// </summary>
  31:         public C Context
  32:         {
  33:             get { return context; }
  34:             set { context = value; }
  35:         }
  36:  
  37:         /// <summary>
  38:         /// Retorna o nome do EntitySet do objeto persistente
  39:         /// </summary>
  40:         private string entitySetName;
  41:  
  42:         /// <summary>
  43:         /// Retorna o nome do EntitySet do objeto persistente
  44:         /// </summary>
  45:         protected string EntitySetName
  46:         {
  47:             get
  48:             {
  49:                 if (String.IsNullOrEmpty(entitySetName))
  50:                 {
  51:                     entitySetName = GetEntitySetName(typeof(E).Name);
  52:                 }
  53:                 return entitySetName;
  54:             }
  55:         }
  56:  
  57:         /// <summary>
  58:         /// Construtor padrão, sem argumentos. Obtém o contexto do Entity Framework usando o ContextManager.
  59:         /// </summary>
  60:         public EFRepository()
  61:         {
  62:             // Obtém o contexto
  63:             this.context = ContextManager.GetContext<C>();
  64:         }
  65:  
  66:         /// <summary>
  67:         /// Insere um novo objeto persistente
  68:         /// </summary>
  69:         /// <param name="entity">Objeto a ser inserido</param>
  70:         public void Insert(E entity)
  71:         {
  72:             context.AddObject(EntitySetName, entity);
  73:         }
  74:  
  75:         /// <summary>
  76:         /// Atualiza um objeto existente.
  77:         /// </summary>
  78:         /// <param name="entity">Objeto persistente</param>
  79:         public virtual void Update(E entity)
  80:         {
  81:             EntityKey key;
  82:             object originalItem;
  83:             if (entity.EntityKey == null)
  84:                 // Obtém o entity key do objeto que será atualizado
  85:                 key = Context.CreateEntityKey(EntitySetName, entity);
  86:             else
  87:                 key = entity.EntityKey;
  88:             try
  89:             {
  90:                 // Obtém o objeto original
  91:                 if (Context.TryGetObjectByKey(key, out originalItem))
  92:                 {
  93:                     if (originalItem is EntityObject &&
  94:                         ((EntityObject)originalItem).EntityState != EntityState.Added)
  95:                     {
  96:                         // Autaliza o objeto
  97:                         context.ApplyPropertyChanges(key.EntitySetName, entity);
  98:                     }
  99:                 }
 100:             }
 101:             catch (Exception ex)
 102:             {
 103:                 throw ex;
 104:             }
 105:  
 106:         }
 107:  
 108:         /// <summary>
 109:         /// Exclui um objeto persistente
 110:         /// </summary>
 111:         /// <param name="entity">Objeto persistente que será excluído</param>
 112:         public void Delete(E entity)
 113:         {
 114:             context.DeleteObject(entity);
 115:         }
 116:  
 117:         /// <summary>
 118:         /// Retorna um objeto que satisfaça a cláusula passada como parâmetro.
 119:         /// </summary>
 120:         /// <param name="expression">Expressão Lambda da cláusula WHERE</param>
 121:         public E SelectOne(Expression<Func<E, bool>> where)
 122:         {
 123:             return context.CreateQuery<E>(EntitySetName).Where(where).FirstOrDefault();
 124:         }
 125:  
 126:         /// <summary>
 127:         /// Salva as alterações no banco de dados.
 128:         /// </summary>
 129:         public void SaveChanges()
 130:         {
 131:             context.SaveChanges();
 132:         }
 133:  
 134:         /// <summary>
 135:         /// Retorna todos os objetos persistentes.
 136:         /// </summary>
 137:         /// <returns>Coleção com todos os objetos</returns>
 138:         public List<E> SelectAll()
 139:         {
 140:             return context.CreateQuery<E>(EntitySetName).ToList();
 141:         }
 142:  
 143:         /// <summary>
 144:         /// Retorna um IQueryable com todos os objetos
 145:         /// </summary>
 146:         /// <returns>IQueryable com todos os objetos</returns>
 147:         public IQueryable<E> QueryAll()
 148:         {
 149:             return context.CreateQuery<E>(EntitySetName).AsQueryable<E>();
 150:         }
 151:  
 152:         /// <summary>
 153:         /// Retorna todos os objetos usando paginação.
 154:         /// </summary>
 155:         /// <param name="maximumRows">Quantidade de objetos por página</param>
 156:         /// <param name="startRowIndex">Linha a partir do qual os objetos serão retornados</param>
 157:         /// <returns>Coleção com todos os objetos</returns>
 158:         public List<E> SelectAll(int maximumRows, int startRowIndex)
 159:         {
 160:             return context.CreateQuery<E>(EntitySetName).Skip<E>(startRowIndex).Take(maximumRows).ToList();
 161:         }
 162:  
 163:         /// <summary>
 164:         /// Retorna um IQueryable com todos os objetos usando paginação
 165:         /// </summary>
 166:         /// <param name="maximumRows">Quantidade de objetos por página</param>
 167:         /// <param name="startRowIndex">Linha a partir do qual os objetos serão retornados</param>
 168:         /// <returns>IQueryable com todos os objetos</returns>
 169:         public IQueryable<E> QueryAll(int maximumRows, int startRowIndex)
 170:         {
 171:             return context.CreateQuery<E>(EntitySetName).Skip<E>(startRowIndex).Take(maximumRows);
 172:         }
 173:  
 174:         /// <summary>
 175:         /// Retorna todos os objetos que satisfaçam a cláusula passada
 176:         /// </summary>
 177:         /// <param name="where">Expressão Lambda da cláusula WHERE</param>
 178:         public List<E> SelectWhere(Expression<Func<E, bool>> where)
 179:         {
 180:             return context.CreateQuery<E>(EntitySetName).Where(where).ToList();
 181:         }
 182:  
 183:         /// <summary>
 184:         /// Retorna todos os objetos que satisfaçam a cláusula passada, usando paginação
 185:         /// </summary>
 186:         /// <param name="where">Expressão Lambda da cláusula WHERE</param>
 187:         /// <param name="maximumRows">Quantidade de objetos por página</param>
 188:         /// <param name="startRowIndex">Linha a partir do qual os objetos serão retornados</param>
 189:         public List<E> SelectWhere(Expression<Func<E, bool>> where, int maximumRows, int startRowIndex)
 190:         {
 191:             return context.CreateQuery<E>(EntitySetName).Where(where)
 192:                           .Skip<E>(startRowIndex).Take(maximumRows).ToList();
 193:         }
 194:  
 195:         /// <summary>
 196:         /// Retorna o número de objetos
 197:         /// </summary>
 198:         /// <remarks>Para ser usado na configuração da paginação</remarks>
 199:         public int GetCount()
 200:         {
 201:             return context.CreateQuery<E>(EntitySetName).Count();
 202:         }
 203:  
 204:         /// <summary>
 205:         /// Retorna o número de objetos que satisfaçam a cláusula passada
 206:         /// </summary>
 207:         /// <remarks>Para ser usado na configuração da paginação</remarks>
 208:         public int GetCount(Expression<Func<E, bool>> where)
 209:         {
 210:             return context.CreateQuery<E>(EntitySetName).Where(where).Count();
 211:         }
 212:  
 213:         /// <summary>
 214:         /// Libera os recursos do Entity Framework.
 215:         /// </summary>
 216:         public void Dispose()
 217:         {
 218:             if (context != null)
 219:                 context.Dispose();
 220:         }
 221:  
 222:         /// <summary>
 223:         /// Retorna o nome do EntitySet, possibilitando a criação de métodos genéricos.
 224:         /// </summary>
 225:         /// <param name="entityTypeName">String com o nome do tipo</param>
 226:         /// <returns>String contendo o EntitySet</returns>
 227:         private string GetEntitySetName(string entityTypeName)
 228:         {
 229:             var container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
 230:             string entitySetName = (from meta in container.BaseEntitySets
 231:                                     where meta.ElementType.Name == entityTypeName
 232:                                     select meta.Name).FirstOrDefault();
 233:             return entitySetName;
 234:         }
 235:  
 236:     }
 237:  
 238:  
 239: }

Na primeira parte deste artigo, criamos o banco de dados e geramos o contexto do Entity Framework para o modelo. Agora, vamos começar a definir o componente para o pattern Repository. Segue uma lista dos requisitos para o componente:

Deverá oferecer a possibilidade de trabalhar independente dos tipos envolvidos

Ou seja, poderemos usar o mesmo componente para persistir qualquer objeto, independente do contexto usado. Isso é facilmente conseguido através do uso de Generic Types, sendo possível parametrizar os tipos que serão trabalhados durante as operações de persistência.

Deverá simplificar o desenvolvimento para as rotinas básicas de persistência

O objetivo será tornar o desenvolvimento o mais simples possível, procurando encapsular todo o “trabalho sujo” e criar uma interface limpa e amigável para o programador.

Baseado nas especificações acima, segue a Interface do componente Repository para o Entity Framework:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Linq.Expressions;
   5:  
   6: namespace Net.Bragil.Data.Repository
   7: {
   8:     /// <summary>
   9:     /// Interface para a implementação do padrão Repository usando o Entity Framework.
  10:     /// </summary>
  11:     /// <typeparam name="E">Tipo do objeto persistente</typeparam>
  12:     /// <typeparam name="C">Tipo do contexto do Entity Framework</typeparam>
  13:     public interface IRepository<E, C>
  14:     {
  15:         /// <summary>
  16:         /// Contexto do Entity Framework
  17:         /// </summary>
  18:         C Context { get; set; }
  19:  
  20:         /// <summary>
  21:         /// Inserção
  22:         /// </summary>
  23:         void Insert(E entity);
  24:  
  25:         /// <summary>
  26:         /// Atualização
  27:         /// </summary>
  28:         void Update(E entity);
  29:  
  30:         /// <summary>
  31:         /// Exclusão
  32:         /// </summary>
  33:         void Delete(E entity);
  34:  
  35:         /// <summary>
  36:         /// Retorna o objeto que satisfaça a cláusula passada como argumento (cláusula WHERE)
  37:         /// </summary>
  38:         E SelectOne(Expression<Func<E, bool>> where);
  39:  
  40:         /// <summary>
  41:         /// Retorna todos os objetos de um tipo
  42:         /// </summary>
  43:         List<E> SelectAll();
  44:  
  45:         /// <summary>
  46:         /// Retorna os objetos usando paginação
  47:         /// </summary>
  48:         List<E> SelectAll(int maximumRows, int startRowIndex);
  49:  
  50:         /// <summary>
  51:         /// Retorna todos os objetos que satisfaçam a cláusula passada
  52:         /// </summary>
  53:         /// <param name="where">Cláusula WHERE</param>
  54:         List<E> SelectWhere(Expression<Func<E, bool>> where);
  55:  
  56:         /// <summary>
  57:         /// Retorna os objetos que satisfaçam a cláusula passada, usando paginação
  58:         /// </summary>
  59:         /// <param name="where">Cláusula WHERE</param>
  60:         List<E> SelectWhere(Expression<Func<E, bool>> where, int maximumRows, int startRowIndex);
  61:  
  62:         /// <summary>
  63:         /// Retorna um objeto IQueryable, possibilitando formar queries usando expressões Lambda
  64:         /// </summary>
  65:         IQueryable<E> QueryAll();
  66:  
  67:         /// <summary>
  68:         /// Retorna um IQueryable com os objetos usando paginação
  69:         /// </summary>
  70:         IQueryable<E> QueryAll(int maximumRows, int startRowIndex);
  71:  
  72:         /// <summary>
  73:         /// Retorna a quantidade de objetos persistentes.
  74:         /// </summary>
  75:         int GetCount();
  76:  
  77:         /// <summary>
  78:         /// Retorna a quantidade de objetos persistentes que satisfaçam a cláusula WHERE
  79:         /// </summary>
  80:         int GetCount(Expression<Func<E, bool>> where);
  81:     }
  82: }

Como você pode ver, o componente oferecerá a maioria dos recursos de persistência, de uma forma muito mais amigável do que usando o Entity Framework diretamente.

Foi necessário também a criação de uma classe auxiliar para instanciação do contexto do Entity Framework usando a estratégia “One-Per-Request”, ou seja, é criada apenas uma instância do contexto por requisição. O objeto do contexto é armazenado em HttpContext.Current.Items, de modo que as chamadas subsequentes dentro da mesma requisição retornam o contexto anteriormente instanciado e armazenado na requisição. Segue o código:

   1: using System.Web;
   2: using System.Data.Objects;
   3:  
   4: namespace Net.Bragil.Data.Context
   5: {
   6:     /// <summary>
   7:     /// Classe gerenciadora do contexto do Entity Framework. Utiliza generics para determinar em tempo de execução o 
   8:     /// tipo do contexto e Reflection para instanciar o tipo genérico. Armazena o contexto no HttpContext, de modo que 
   9:     /// não seja necessário instanciar o contexto nas próximas chamadas dentro da requisição web.
  10:     /// </summary>
  11:     public sealed class ContextManager
  12:     {
  13:         /// <summary>
  14:         /// Construtor privado, não será possível instanciar a classe usando new
  15:         /// </summary>
  16:         private ContextManager()
  17:         {
  18:         }
  19:  
  20:         /// <summary>
  21:         /// Obtém o contexto do Entity Framework usando generics.
  22:         /// </summary>
  23:         /// <typeparam name="T">Tipo do contexto do Entity Framework (deve herdar de ObjectContext)</typeparam>
  24:         /// <returns>Contexto do Entity Framework</returns>
  25:         public static T GetContext<T>()
  26:             where T: ObjectContext
  27:         {
  28:             string ocKey = "ocm_" + HttpContext.Current.GetHashCode().ToString("x");
  29:             if (HttpContext.Current != null)
  30:             {
  31:                 if (!HttpContext.Current.Items.Contains(ocKey))
  32:                 {
  33:                     // Instancia o contexto através de Reflection
  34:                     T ctx = typeof(T).GetConstructor(System.Type.EmptyTypes).Invoke(System.Type.EmptyTypes) as T;
  35:                     // Armazena na requisição
  36:                     HttpContext.Current.Items.Add(ocKey, ctx);
  37:                 }
  38:                 return HttpContext.Current.Items[ocKey] as T;
  39:             }
  40:             else
  41:                 // Caso a aplicação não seja web, instancia e retorna o contexto.
  42:                 return typeof(T).GetConstructor(System.Type.EmptyTypes).Invoke(System.Type.EmptyTypes) as T;
  43:             
  44:         }
  45:  
  46:     }
  47: }

Já estamos quase lá! No próximo artigo vamos desenvolver a implementação da classe EFRepository.


Este é o primeiro de uma série de artigos abordando a criação de um Repository para o Entity Framework. Para quem não sabe, o Repository é um Design Pattern que abstrai as complexidades da camada de acesso a dados, oferendo uma interface de mais alto nível que possibilita um menor acoplamento com a tecnologia de persistência. Para um melhor entendimento, vamos apresentar de forma prática a criação da camada de persistência para um modelo simples de banco de dados, a geração do contexto do Entity Framework, o desenvolvimento do Repository e finalmente um pequeno exemplo mostrando os recursos do componente criado.

Os requisitos para o desenvolvimento proposto são os seguintes:

  • Visual Studio 2008 SP1
  • .Net Framework 3.5
  • Banco de dados SQL Server Express Edition

Obs: é importante salientar que há outros bancos de dados cujos drivers possuem suporte ao Entity Framework. Verifique se o banco de dados que você usa possui driver para .Net com suporte ao Entity Framework.

Vamos trabalhar com um modelo de dados bem simples, mas suficiente para o nosso exemplo, conforme segue:

 

db

Basicamente, um cadastro simples de pessoas com endereço completo. Depois de definido o modelo de dados, vamos realizar a geração do contexto do Entity Framework (edmx). Se você não sabe como fazer isso, leia este artigo.

edmx

Pronto, já temos o banco de dados e o contexto do Entity Framework. O tipo do objeto de contexto do Entity Framework foi nomeado como DbEntities.

entities

No próximo artigo vamos abordar o desenvolvimento do componente Repository.


Páginas

Calendário

«  March 2010  »
MoTuWeThFrSaSu
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234
View posts in large calendar