Existe uma vantagem real no repositório genérico?


28

Estava lendo alguns artigos sobre as vantagens da criação de repositórios genéricos para um novo aplicativo ( exemplo ). A ideia parece boa porque me permite usar o mesmo repositório para fazer várias coisas para vários tipos de entidade diferentes ao mesmo tempo:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

No entanto, o restante da implementação do Repositório depende muito do Linq:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

Esse padrão de repositório funcionou de maneira fantástica para o Entity Framework e praticamente ofereceu um mapeamento de 1 para 1 dos métodos disponíveis no DbContext / DbSet. Mas, dada a lenta adoção do Linq em outras tecnologias de acesso a dados fora do Entity Framework, que vantagem isso oferece sobre o trabalho diretamente com o DbContext?

Tentei escrever uma versão PetaPoco do Repositório, mas o PetaPoco não suporta Linq Expressions, o que torna a criação de uma interface genérica de IRepository praticamente inútil, a menos que você a use apenas para GetAll, GetById, Adicionar, Atualizar, Excluir e Salvar. métodos e utilizá-lo como uma classe base. Então você deve criar repositórios específicos com métodos especializados para lidar com todas as cláusulas "where" que eu poderia transmitir anteriormente como predicado.

O padrão do Repositório Genérico é útil para qualquer coisa fora do Entity Framework? Caso contrário, por que alguém usaria isso em vez de trabalhar diretamente com o Entity Framework?


O link original não reflete o padrão que eu estava usando no meu código de exemplo. Aqui está um ( link atualizado ).



11
A questão é realmente sobre "advatange do repositório genérico" ou é mais "como ocultar consultas complexas por trás de uma interface de repositório genérico"? É possível ser genérico se sua interface e uso dependerem do linq? Nosso Repositoryapi possui um método QueryByExample que é completamente independente da técnica de pesquisa e permitiria mudar a implementação.
K3b

"Permite-me usar o mesmo repositório para fazer várias coisas para vários tipos de entidades" => Sou eu ou você apenas entendeu mal o que é um repositório genérico (inclusive no que diz respeito ao artigo mencionado)? Para mim, repositório genérico sempre significou templates todos os repos com uma única classe ou interface, não ter um único repositório instância gerenciar a persistência de todas as entidades independentemente do seu tipo ...
guillaume31

Respostas:


35

O repositório genérico é ainda inútil (e o IMHO também é ruim) para o Entity Framework. Não traz nenhum valor adicional ao que já é fornecido por IDbSet<T>(que é o repositório btw. Generic).

Como você já encontrou o argumento de que o repositório genérico pode ser substituído pela implementação de outra tecnologia de acesso a dados, é bastante fraco, pois pode exigir a gravação do seu próprio provedor Linq.

O segundo argumento comum sobre o teste de unidade simplificado também está errado porque a zombaria do repositório / conjunto com armazenamento de dados na memória substitui o provedor Linq por outro que possui recursos diferentes. O provedor Linq para entidades suporta apenas subconjuntos de recursos do Linq - ele ainda não suporta todos os métodos disponíveis na IQueryable<T>interface. O compartilhamento de árvores de expressão entre a camada de acesso a dados e a lógica de negócios evita a falsificação da camada de acesso a dados - a lógica da consulta deve ser separada.

Se você deseja ter uma abstração "genérica" ​​forte, também deve envolver outros padrões. Nesse caso, você precisa usar uma linguagem de consulta abstrata que pode ser traduzida pelo repositório para uma linguagem de consulta específica suportada pela camada de acesso a dados usada. Isso é tratado pelo padrão de especificação. Linq on IQueryableé especificação (mas a tradução requer provedor - ou algum visitante personalizado que traduza a árvore de expressão em consulta), mas você pode definir sua própria versão simplificada e usá-la. Por exemplo, o NHibernate usa a API de critérios. Ainda assim, a maneira mais simples é usar repositório específico com métodos específicos. Dessa maneira, é o mais simples de implementar, o mais simples de testar e o mais simples de falsificar em testes de unidade, porque a lógica da consulta está completamente oculta e separada por trás da abstração.


Apenas uma pergunta rápida, o NHibernate tem o ISessionque é facilmente ridicularizável para fins gerais de teste de unidade, e eu ficaria feliz em deixar o 'repositório' ao trabalhar com a EF também - mas existe alguma maneira mais fácil e direta de recriar isso? Algo semelhante a ISessionea ISessionFactory', porque não há IDbContext, tanto quanto eu posso dizer ...
Patryk Ćwiek

Não, não há IDbContext- se você quiser, IDbContextpode simplesmente criar um e implementá-lo no seu contexto derivado.
Ladislav Mrnka

Então, você está dizendo que, para aplicativos MVC todos os dias, o IDbSet <T> deve fornecer um repositório genérico suficientemente bom para esquecer completamente o padrão do repositório. Exceto para situações especiais, por exemplo, onde você não tem permissão para referências DAL no seu projeto MVC.
ProfK

11
Boa resposta. Alguns desenvolvedores criam apenas outra camada de abstrações sem qualquer motivo. IMO, EF não precisa repositório genérico nem uma unidade de trabalho (são build-in)
Ashraf

11
IDbSet <T> é um repositório genérico com uma dependência no Entity Framework que meio que anula o propósito de usar um repositório genérico para abstrair a dependência no Entity Framework.
Joel McBeth

7

O problema não é o padrão do repositório. Ter uma abstração entre obter dados e como você os obtém é uma coisa boa.

A questão aqui é a implementação. Assumir que uma expressão arbitrária funcione para a filtragem é arriscado, na melhor das hipóteses.

Fazer um repositório funcionar para todos os seus objetos não faz sentido. Os objetos de dados raramente, se alguma vez, são mapeados diretamente para objetos de negócios. Passar T para filtrar faz muito menos sentido nessas situações. E o fornecimento de tanta funcionalidade garante que você não poderá suportar tudo isso depois que um provedor diferente aparecer.


Portanto, faz mais sentido ter um Repositório por objeto de dados ou grupo de objetos fortemente relacionados, com métodos específicos, como GetById (int id), SortedList (), etc? Estou retornando listas de objetos de dados de um repositório ou transformando-os em objetos de negócios com os campos necessários também? Meio que pensei que foi o que aconteceu na camada Serviço / Negócios.
Sam

11
@ sam - eu tendem a favorecer repositórios mais específicos, sim. Se eles estão traduzindo ou não, depende de onde as coisas provavelmente mudarão. Se seu domínio estiver bem definido, eu retornaria as coisas na forma do domínio. Se os dados estiverem bem definidos, eu retornaria as coisas na forma das estruturas de dados. Caso contrário, eu teria uma entidade que serve como base bem definida para construir e depois me adaptar a isso.
Telastyn

11
Não há razão para que você não possa ter repositórios específicos que delegam as coisas comuns a um repositório genérico, mas também ter métodos específicos específicos ao repositório para chamadas mais complexas. Eu já vi isso acontecer várias vezes.
Eric King

@EricKing eu também tenho, e foi bom lá, mas tende a vazar alguma abstração, já que o material comum só existe devido à semelhança de como os dados são armazenados (GetByID, por exemplo, requer tabelas com IDs).
Telastyn

@Telastyn Sim, eu concordo com isso, mas isso acontece com repositórios específicos também. Abstrações com vazamento não são específicas para repositórios genéricos. (Uau, eu poderia ter formulado mais isso desajeitadamente?)
Eric Rei

2

O valor de uma camada de dados genérica (um repositório é um tipo específico de camada de dados) está permitindo que o código altere o mecanismo de armazenamento subjacente com pouco ou nenhum impacto no código de chamada.

Em teoria, isso funciona bem. Na prática, como você observou, a abstração geralmente é vazada. Os mecanismos usados ​​para acessar dados em um são diferentes dos mecanismos em outro. Em alguns casos, você acaba escrevendo o código duas vezes: uma vez na camada de negócios e repetindo-o na camada de dados.

A maneira mais eficaz de criar uma camada de dados genérica é conhecer os diferentes tipos de fontes de dados que o aplicativo usará anteriormente. Como você viu, supor que LINQ ou SQL seja universal pode ser um problema. Tentar modernizar novos armazenamentos de dados provavelmente resultará em uma reescrita.

[Editar: adicionado o seguinte.]

Também depende do que o aplicativo precisa da camada de dados. Se o aplicativo estiver apenas carregando ou armazenando objetos, a camada de dados pode ser muito simples. À medida que a necessidade de pesquisar, classificar e filtrar aumenta, a complexidade da camada de dados aumenta e as abstrações começam a ficar vazadas (como expor as consultas LINQ na pergunta). Uma vez que os usuários possam fornecer suas próprias consultas, no entanto, o custo / benefício da camada de dados precisa ser cuidadosamente ponderado.


1

Ter uma camada de código acima do banco de dados vale a pena em quase todos os casos. Geralmente, eu prefiro um padrão "GetByXXXXX" no referido código - ele permite otimizar as consultas por trás dele, conforme necessário, mantendo a interface do usuário livre de desordem na interface de dados.

Tirar proveito dos genéricos é definitivamente um jogo justo - ter um Load<T>(int id)método faz muito sentido. Mas criar repositórios em torno do LINQ é o equivalente em 2010 a eliminar consultas sql em qualquer lugar com um pouco de segurança adicional.


0

Bem, com o link fornecido, posso ver que pode ser um invólucro útil para a DataServiceContext, mas não reduz as manipulações de código nem melhora a legibilidade. Além disso, o acesso a DataServiceQuery<T>está obstruído, o que limita a flexibilidade de .Where()e .Single(). Nem são AddRange()fornecidas alternativas. Também não é Delete(Predicate)fornecido o que poderia ser útil ( repo.Delete<Person>( p => p.Name=="Joe" );para excluir Joe-s). Etc.

Conclusão: essa API obstrui a API nativa e a limita a algumas operações simples.


Você está certo. Esse não foi o artigo usando o padrão que tenho no meu código de exemplo. Tentarei encontrar o link quando chegar em casa.
Sam

Link atualizado adicionado ao final da pergunta.
Sam

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.