PantaNet

ADO.NET Entity Framework 4.1 - parte 5 - DbContext Factory e Repository Pattern

Os posts anteriores introduziram o EF 4.1 Code First, desde a configuração das classes e mapeamento com o banco de dados, até operações CRUD e validação. Os próximos posts não serão mais introdutórios, começaremos agora a avançar. Nesse post vou apresentar a implementação de uma factory para criação e controle de instâncias de contextos, e uma implementação simples do padrão repositório com EF.

 

1 - DbContextFactory

 

Todos os exemplos de consultas que mostrei nos artigos anteriores envolvem a utilização do DbContext dentro de um bloco using onde um novo contexto é instanciado, utilizado e finalmente descartado ao final do bloco, pois o DbContext implementa a interface IDisposable.

Para reaproveitar o mesmo DbContext em várias chamadas, devemos declarar uma variável e utilizá-la sempre que necessário conforme o código abaixo:

 



Cada instância de um DbContext possui seu próprio cache de entidades. A cada consulta feita ao banco de dados, o DbContext guarda as referências das entidades recuperadas, fazendo o mesmo com novas entidades que forem adicionadas. 

Para aproveitar o cache que o DbContext disponibiliza, devemos instanciar o contexto e guardar sua referência em uma variável ou propriedade, evitando que o garbage collector destrua os objetos. Assim, os objetos que são adicionados ao contexto permanecem "vivos", podendo ser reutilizados durante o ciclo de vida da aplicação ou em Requests subsequentes numa aplicação ASP.NET.

 

Uma forma de gerenciar os DbContexts que são instanciados é armazenando suas referências em uma propriedade do tipo Dictionary em uma classe Singleton.

 

O diagrama abaixo mostra como fica a arquitetura utilizando uma classe Factory que cria e armazena DbContexts que serão solicitados pelas telas/páginas ASP.NET:

 

 

Com essa arquitetura, ao invés de instanciarmos diretamente um Contexto dentro de uma página ASP.NET, iremos solictar o DbContext à nossa classe factory, que irá retornar um DbContext já existente ou fabricar um novo caso ele não seja encontrado em seu dicionário. Cada DbContext que for criado receberá um nome único que será sua chave de identificação dentro do dicionário. Esse nome pode ser o nome da página ou o Id de um usuário, por exemplo. Assim, o DbContext será compartilhado em várias requisições, evitando que o EF tenha que ir ao banco de dados buscar novamente os mesmos dados que já foram recuperados anteriormente (cache).

 

Implementei a factory usando o padrão Singleton para que exista apenas uma instância em toda a aplicação:

 

 

Para recuperar um contexto através da factory, simplesmente passamos como parâmetro genérico o tipo do Contexto que desejamos e a chave de identificação e em seguida invocar o método GetOrCreateContext. A factory vai procurar no dicionário um DbContext através da chave informada. Se encontrar, irá retornar a referência para essa instância, caso contrário, irá criar uma nova instância do DbContext, armazenar no dicionário e retornar. A figura abaixo mostra o código:

 

Veja um teste que escrevi para provar que a factory funciona como esperado:

 

 

Coloquei um breakpoint quando o método GetOrCreateContext for invocado. Veja que o dicionário contém 3 instâncias diferentes de DbContext, que se diferenciam pelas chaves: "chaveA", "chaveB" e "chaveC".

 


O resultado do teste ficou como esperado:

 

 

2 - Repository Pattern

 

O padrão repositório é considerado chave quando se trata de DDD (Domain Driven Design). Não vou me aprofundar muito nesse assunto pois existe muito material na internet, mas só para resumir, utilizando esse padrão a aplicação não faz consultas diretamente à camada de acesso aos dados, ao invés disso, ela faz consulta à coleções de objetos do repositório. A implementação do repositório é que se encarrega de buscar os objetos na fonte de dados. Assim, a aplicação não se preocupa de onde vem os dados pois essa função passa a ser do repositório, que pode buscar as informações de um banco de dados, arquivos XML, webservices, etc.

 

 

Existem muitas implementações desse padrão. Vou mostrar neste post a minha versão, que considero estável e funcional e utilizo em meus projetos. O contrato ficou assim:

 

A implementação utiliza a DbContextFactory para criar o contexto do tipo passado no parâmetro genérico TDbContext, e o parâmetro T se refere ao tipo da classe POCO.

 

 

Agora vamos demonstrar as operações CRUD com esse repositório:

 

Veja o intelissense do VisualStudio mostrando os métodos do repositório:

 

Operações de Update e Delete:

 

 

Vantagem da utilização do repositório

 

Na minha opinião, utilizando essa implementação do repositório eu tenho a vantagem de utilizar os métodos que já estão implementados, e posso também criar uma implementação "Fake" para ser usado em testes, sem banco de dados.

 

Aqui vai uma implementação Fake do mesmo repositório: RepositorioFake<T>

 

Ao invés dessa implementação utilizar dados do banco de dados, ele mantém um List<T> com os objetos. Os mesmos métodos estão disponíveis pois implementa a mesma interface:

 

 

 

FIM

 

Apresentei nesse post minhas soluções na implementação de uma classe singleton que fabrica e armazenda contextos baseados em DbContext do EF 4.1 e uma implementação de um repositório. Vou disponibilizar o link para baixar a solution completa. Espero que tenham gostado. Por hoje é só!

 

Exibições: 2970

Tags: Code, DDD, Entity, Factory, First, Framework, Mock, Repositorio, Singleton, TDD

Comentário de Jone em 22 abril 2011 às 11:44
Comentário de Jone em 25 maio 2011 às 0:23
Olá Felipe, a chave do repositório não tem nada haver com a chave da sua tabela no banco de dados, essa chave do repositório é um nome que identifica exclusivamente um repositório, por exemplo eu posso ter várias instâncias de um repositório com chaves diferentes. A chave você pode passar uma string vazia ( "" ). Outra coisa, quando você utiliza o método Novo() do repositório, ele já anexa a entidade ao Contexto, então não é preciso adicionar novamente pelo método Adicionar(entidade), só utilize o Adicionar() caso você tenha criado a entidade por conta própria usando new (). A princípio dei uma olhada no seu código e deveria funcionar. Caso não descubra o que está errado, envie o projeto para que eu possa dar uma olhada. Abraço
Comentário de Felipe Pimentel Augusto em 25 maio 2011 às 10:00

Fala Jone, cara, muito obrigado pela sua atencao,

O meu projeto funcionou já, realizei algumas alterações, mas o erro era incrivelmente no banco de dados.

Criei um novo banco, com outro nome mas com as mesmas tables e columns e funcionou,...
Parabens pelo artigo.

Comentário de Felipe Pimentel Augusto em 25 maio 2011 às 10:26

Jone,

Utilizando essa arquitetura, como eu posso fazer chamadas a store procedures ?

E parabens pelo Post !

Comentário de Jone em 25 maio 2011 às 20:24
Sim pode chamar StoredProcedures acessando o repositório com _repo.GetQueryable().SqlQuery("nomedaSP").ToList();
Comentário de Felipe Pimentel Augusto em 26 maio 2011 às 18:00

na verdade Jone, eu fiz o seguinte para chamar as procedures passando os paramentros....

na Interface do Repositorio eu coloquei + um contrato:

int ExecutarSP(String procName, List<SqlParameter> param);

 

Na classe que implementa a Interface eu fiz o seguinte:

public virtual int ExecutarSP(String procName, List<SqlParameter> param)
        {
            StringBuilder Comando = new StringBuilder();
            Comando.Append("EXEC ");
            Comando.Append(procName + " ");

            foreach (SqlParameter item in param)
            {
                Comando.Append(item.ParameterName + ", ");
            }

            if (Comando.ToString().EndsWith(", "))
                Comando = Comando.Remove(Comando.Length - 2, 2);

            return _dbContext.Database.ExecuteSqlCommand(Comando.ToString(), param.ToArray());
        }

e na tela eu chamo o metodo assim:

_repProfissao.ExecutarSP("spCadastro", param);

sendo o param o seguinte:

List<SqlParameter> listaParametros = new List<SqlParameter>();

 

Que tal ? 

Comentário de Felipe Pimentel Augusto em 27 maio 2011 às 16:51

Bem, apos algumas tentativas eu resolvi postar meu problema aqui ...

eu estou trabalhando com o Entity Framework 4.1 estou com o seguinte impasse.

Tenho algumas colunas de algumas tabelas que apesar de serem PK's elas n"ao são auto-incremento, ou seja, eu TERIA que ler o ultimo registro para incrementar um no id da PK para entao conseguir persistir os dados certo ? na verdade eu queria saber se tem algum modo de fazer isso automatico com o EF4.1, como um @@identity.

 

Minha classe POCO esta assim:

[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int tipa_id { get; set; }

ja tentei colocar como DatabaseGeneratedOption.Computed , mas nao funcionou ...

Alguem pode me ajudar, tem alguma dica ?

Comentário de Hugo Campos em 13 julho 2011 às 16:04
Boa tarde Jone tudo certo. Cara eu to fazendo vendo esse repository generico que vc crio, eu fiz aqui e é claro q deu certo, Só que agora eu to tentando fazer um repositorio especifico com algumas informações herdando do repositorio.generico só que nw to conseguindo sera que vc poderia me da uma ajudinha? Brigadão.
Comentário de Felipe Pimentel Augusto em 17 julho 2011 às 19:08

Na verdade Jone, eu acho que seria interessando implementar uma classe abstrata herdando da interface, implementando os contratos da interface.

Essa classe abstrata seria herdada pela classe concreta sobreescrevendo os metodos quando necessario.

 

Comentário de Jone em 17 julho 2011 às 21:21

Na verdade seria mais simples alterar os métodos que você deseja "personalizar" para virtual, aí não precisa criar uma classe abstrata só pra isso.

 

Por exemplo: 

 

public virtual IEnumerable<T> ListarMuitos(Func<T, bool> where)       

{           

return Dbset.Where(where).ToList();       

}

 

 

Aí na classe derivada que você criar, basta dar um override:

 

public override IEnumerable<T> ListarMuitos(Func<T, bool> where)       

{           

//personalizar aqui.

base.ListarMuitos();

}

Comentar

Você precisa ser um membro de PantaNet para adicionar comentários!

Entrar em PantaNet

Fotos

Carregando...
  • Adicionar fotos
  • Exibir todos

Apoio





 

© 2012   Criado por Gustavo Malheiros.

Badges  |  Relatar um incidente  |  Termos de serviço