Ultimamente tenho vários exemplos de Repository Pattern homemade nas questões sobre Entity Framework.
Muitos destes repositórios, atuam como um falso repositório que encapsula o repositório verdade(DbContent) para executar operações básicas, como GetAll, Atualizar, Inserir, etc.
Desta forma, não consigo ver benefícios neste tipo de implementação, podendo até trazer uma complexibilidade maior ao projeto em si.
Seria Repository Pattern um novo Hungarian Notation, onde muitos programadores ainda colocam abreviações do Type como prefixo da variável em linguagens com OO, quando este tipo de notação deveria mencionar a natureza da variável como prefixo e é desnecessária em linguagens OO?
Talvez eu não esteja conseguindo ver fora da caixa, então gostaria de exemplos com Repository Pattern que agregem valor.
Esta resposta fala de situações onde repositórios agregam valor: pt.stackoverflow.com/a/51439 – Caffé 26/03/15 às 22:16
@Rod, sabe o habito de colocar uma abreviação do tipo do objeto no nome da variavel? tipo var strNome = "Toby Mosque", isto é o que alguns entendem por Notação Hungara... enquanto que a intenção atual era usar uma abreviação do tipo do dado na variavel, como por exemplo um dado seguro s ou não seguro u, como no exemplo: var sValue = 515; //endereço seguro em memoria e var uValue = 6874168416; //endereço não seguro... Em todo caso este habito é desnecessário para linguagens modernas, sendo útil apenas para linguagens de mais baixo nível. – Tobias Mesquita 29/10/15 às 11:45
@TobyMosque entendi, nossa, nunca me passou na cabeça em usar isso kkkk nem do tempo do vb6 não usava isso hauhauha – Rod 29/10/15 às 12:34
@Rod, mas com certeza você já viu alguém utilizando, provavelmente sem entender o por que daquilo. – Tobias Mesquita 29/10/15 às 12:42
Simples assim. Implementar um repositório em cima de outro não faz o menor sentido. Isto fica fácil de ver analisando como se comporta o DbSet.
Aqui não estou executando coisa alguma. Estou apenas obtendo um objeto que, na resolução dele para uma enumeração, me devolverá objetos cujo valor de EntidadeId seja maior que 25. Posso ir acumulando predicados sem executar o código. Suponha o objeto query do exemplo anterior, posso fazer o seguinte:
Nas implementações que vejo por aí, todos os métodos resolvem o retorno para IEnumerable, o que força o banco de dados a executar a resolução da enumeração (portanto, o SQL), acumulando resultados supérfluos em memória e aumentando o tempo de execução da pesquisa porque o resultado é desnecessariamente maior.
Abaixo explico as grandes falácias de quem defende repositório em cima de Entity Framework, bem como dou exemplos do exposto acima para que a explicação fique mais clara.
O exemplo fala por si. Articles é um DbContext, que implementa IQueryable (que implementa IEnumerable). Ao retornar IEnumerable, estamos forçando a conversão de IQueryable em IEnumerable, ou seja, executando a consulta inteira (como um TABLE SCAN, aliás, porque não há predicados de filtro definidos).
Além de ser um encapsulamento inútil, força o banco a trabalhar errado, faz a aplicação trabalhar errado e acumula memória pra operações que não precisa.
Se a lógica complexa é repetida várias vezes, a solução então é padronizar 100% do sistema (que possivelmente é 20% ou menos que é realmente complexo) ou só os 20% complexo?
Lembrando que não preciso usar .ToList() até o momento em que preciso resolver a enumeração. Ou seja, a consulta pode ser executada apenas no último momento, tirando total vantagem do comportamento preguiçoso implementado no framework.
Esta eu gosto muito. O mais fácil é refatorar as declarações de contexto, por exemplo, esta de uma aplicação que usa um contexto Identity:
IApplicationDbContext seria a extração da interface do seu contexto (que é um repositório) aí sim podemos implementar um repositório em cima desta nova interface, ou seja:
Obviamente, os DbSets precisam ser reescritos. Não recomendo usar apenas IDbSet porque alguns métodos de extensão só existem em DbSet. Melhor extrair a interface de DbSet garantindo os métodos de extensão.
Falácia 4: "O EF encapsula a abstração do repositório e a infraestrutura, então é preciso uma camada extra pra separar a lógica de negócio da infraestrutura."
Errado. Veja que a implementação tecnológica é separada da abstração do repositório desde a versão 6.
Além disso, se fosse desta forma, não seria possível, por exemplo, trocar a connection factory. Veja alguns exemplos:
Falácia 5: "Não usar repositório com Entity Framework causa alto acoplamento e fere os princípios do SOLID e da boa programação OO".
S - Single Responsibility Principle, Princípio da Responsabilidade Única: Uma classe deve uma única responsabilidade, e não ser um canivete suíço com milhares de funções;
O - Open Closed Principle, Princípio do Aberto/Fechado: Em herança, as classes derivadas devem evitar ter métodos e elementos peculiares quando públicos. Melhores alternativas são o poliformismo, o uso de interfaces (contratos) e extensões (Extensions, quando houver);
L - Liskov Substitution Principle, Princípio da Substituição de Liskov: Se temos duas classes, pai e filha, então os objetos da classe pai, em um programa, podem ser substituídos pelos objetos da classe filha sem que seja necessário alterar as propriedades deste programa;
I - Interface Segregation Principle, Princípio da Segregação de Interface: Uma interface não deve ser gorda, ou seja, os objetos que a implementam não devem precisar implementar uma porção de métodos que, em sua maioria, não serão usados;
D - Dependency Inversion Principle, Princípio da Inversão de Dependência: Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações, ou seja, se uma classe ou funcionalidade é usada em muitos lugares com diversas possibilidades de classes, ela deve ser substituída por um termo genérico ou uma interface.
Aqui deve-se notar que a implementação de um repositório em cima do Entity Framework fere, em primeiro lugar, o Princípio da Segregação de Interface, pois as operações CRUD são implementadas por métodos que já existem:
Sobre alto acoplamento, o único acoplamento que existe é entre o contexto e suas classes de domínio, que justamente foram feitas para moldar o comportamento do contexto. Ou seja, ainda que se reduza o acoplamento usando interfaces, essa geração de interfaces é inútil porque Models só fazem sentido quando usados juntamente com o contexto.
Sobre o princípio da substituição de Liskov, uma simples extração de interface de DbSet mostra que podemos implementar um repositório da forma com que desejarmos sem prejuízo de funcionalidades. Há outros prejuízos como a perda da geração de SQL por métodos de extensão de filtragem, mas a implementação é possível. Há apenas uma ressalva aqui: a própria Microsoft não respeitou este princípio, pois DbSet possui uma implementação muito mais rica que IDbSet no quesito métodos.
Sobre o Princípio do Aberto/Fechado, o Entity Framework suporta herança e composição. Quase todos os métodos que interagem com DbSet e DbContext são extensões, então a substituição deles é muito simples, bastando extrair a interface e reescrever o método de pesquisa em banco.
Sobre o Princípio da Responsabilidade Única, DbContext é um observador de DbSets, e apenas isso, e DbSet é uma coleção de registros de uma determinada entidade com as 4 operações básicas de um banco de dados: selecionar, inserir, editar e excluir.
Sobre o Princípio de Inversão de Dependência, isto pode ser resolvido por injeção de dependência, mas isso apenas se o time de programação fizer muita questão, porque um DbContext deve existir apenas durante o ciclo de uma requisição e, portanto, instanciado com ela e eliminado com ela.
Concluindo, o Entity Framework não fere o SOLID, salvo a exceção que falei de DbSet. Não fere princípios de OO. Não causa alto acoplamento.
A carga preguiçosa (Lazy Load) é definida por padrão no Entity Framework. O que acontece é que alguns desligam a carga preguiçosa pra definir a estratégia de carga no repositório:
Não sei o sentido disso. O correto seria o programador dizer quando a carga antecipada será usada. Isso é feito através do método de extensão Include ou pelo método nativo Include.
A grande diferença aqui é que Include, num contexto de objeto IQueryable, é pré-compilada pelo Linq para gerar uma sentença SQL com JOIN, e não simplesmente usar uma operação em memória.
Supondo uma aplicação que utilize repositório em cima de Entity Framework e tenha um código mais ou menos assim:
Chama-se Problema do Contexto Destacado porque dois objetos estão sendo observados por contextos diferentes, sendo que o correto é haver apenas um contexto de dados por requisição. Ou seja, o objeto entidadeDependente está em um outro contexto, destacado do contexto do objeto principal, entidade.
É apenas mais um exemplo do uso errado do Entity Framework, que atua como observador de objetos, monitorando suas modificações e as persistindo quando SaveChanges() é chamado.
A seguinte pesquisa tem alguns resultados sobre este problema. Há também esta pesquisa, em que o problema é tratado com outros nomes.