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 ).
Respostas:
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.fonte
ISession
que é 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 aISession
eaISessionFactory
', porque não háIDbContext
, tanto quanto eu posso dizer ...IDbContext
- se você quiser,IDbContext
pode simplesmente criar um e implementá-lo no seu contexto derivado.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.
fonte
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.
fonte
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.fonte
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 aDataServiceQuery<T>
está obstruído, o que limita a flexibilidade de.Where()
e.Single()
. Nem sãoAddRange()
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.
fonte
Eu diria "sim". Dependendo do seu modelo de dados, um Repositório Genérico bem desenvolvido pode tornar a camada de acesso a dados mais enxuta, organizada e muito mais robusta.
Leia esta série de artigos (de @ chris-pratt ):
fonte