Eu tenho visto muitos projetos que têm repositórios que retornam instâncias de IQueryable
. Isso permite que filtros adicionais e classificação possam ser executados IQueryable
por outro código, que se traduz em diferentes SQL sendo gerados. Estou curioso de onde veio esse padrão e se é uma boa ideia.
Minha maior preocupação é que um IQueryable
é uma promessa de atingir o banco de dados algum tempo depois, quando for enumerado. Isso significa que um erro seria lançado fora do repositório. Isso pode significar que uma exceção do Entity Framework é lançada em uma camada diferente do aplicativo.
Também já tive problemas com o MARS (Multiple Active Result Sets) no passado (especialmente ao usar transações) e essa abordagem parece que levaria a isso acontecer com mais frequência.
Eu sempre liguei AsEnumerable
ou ToArray
no final de cada uma das minhas expressões LINQ para garantir que o banco de dados seja atingido antes de sair do código do repositório.
Gostaria de saber se o retorno IQueryable
poderia ser útil como um bloco de construção para uma camada de dados. Eu vi um código bastante extravagante com um repositório chamando outro repositório para criar um ainda maior IQueryable
.
fonte
where
cláusula a um adiadoIQueryable
, você só precisará enviar esses dados pela conexão, não todo o conjunto de resultados.Respostas:
O retorno do IQueryable definitivamente proporcionará mais flexibilidade aos consumidores do repositório. Isso coloca a responsabilidade de reduzir os resultados para o cliente, o que naturalmente pode ser um benefício e uma muleta.
No lado bom, você não precisará criar toneladas de métodos de repositório (pelo menos nessa camada) - GetAllActiveItems, GetAllNonActiveItems, etc. - para obter os dados desejados. Dependendo da sua preferência, novamente, isso pode ser bom ou ruim. Você vai (/ deve) precisa definir contratos comportamentais que suas implementações aderem, mas onde isso vai é até você.
Portanto, você pode colocar a lógica de recuperação fora do repositório e deixá-la ser usada da maneira que o usuário desejar. Portanto, a exposição do IQueryable oferece a maior flexibilidade e permite consultas eficientes, em oposição à filtragem na memória, etc., e pode reduzir a necessidade de criar vários métodos específicos de busca de dados.
Por outro lado, agora você deu a seus usuários uma espingarda. Eles podem fazer coisas que você pode não ter planejado (usar excessivamente .include (), fazer consultas pesadas e filtrar na memória em suas respectivas implementações, etc.), o que basicamente ajudaria os controles de camadas e comportamentais porque você forneceu acesso total.
Portanto, dependendo da equipe, da experiência, do tamanho do aplicativo, das camadas e da arquitetura gerais ... depende: - \
fonte
Expression<Func<Foo,bool>>
para o seu método. Esses parâmetros podem ser passadosWhere
diretamente para o método, permitindo ao consumidor escolher seu filtro sem precisar de acesso direto ao IQueryable <Foo>.Expor IQueryable a interfaces públicas não é uma boa prática. O motivo é fundamental: você não pode fornecer a realização IQueryable como é dito.
Interface pública é um contrato entre provedor e clientes. E na maioria dos casos, há uma expectativa de implementação completa. IQueryable é uma fraude:
O padrão do repositório nos dá uma representação clara por camadas e, mais importante, pela independência da realização. Para que possamos mudar na fonte de dados futura.
A pergunta é " Deve haver possibilidade de substituição da fonte de dados? ".
Se amanhã parte dos dados puder ser movida para serviços SAP, banco de dados NoSQL ou simplesmente para arquivos de texto, podemos garantir a implementação adequada da interface IQueryable?
( mais pontos positivos sobre por que isso é uma má prática)
fonte
Realisticamente, você tem três alternativas se desejar uma execução adiada:
IQueryable
.GetCustomersInAlaskaWithChildren
, abaixo)IQueryable
internamente.Prefiro o terceiro (apesar de ter ajudado colegas a implementar o segundo também), mas obviamente há alguma configuração e manutenção envolvidas. (T4 para o resgate!)
Edit : Para esclarecer a confusão em torno do que estou falando, considere o seguinte exemplo roubado do IQueryable: Can Kill Your Dog, Steal Your Wife, Kill Your Will to Live, etc.
No cenário em que você exporia algo como isto:
a API da qual estou falando permitiria que você expusesse um método que permitisse expressar
GetCustomersInAlaskaWithChildren
(ou qualquer outra combinação de critérios) de maneira flexível, e o repositório o executaria como um IQueryable e retornaria os resultados para você. Mas o importante é que ele seja executado dentro da camada do repositório, para que ainda aproveite a execução adiada. Depois de recuperar os resultados, você ainda poderá vincular o LINQ-to-objects ao conteúdo do seu coração.Outra vantagem de uma abordagem como essa é que, por serem classes POCO, esse repositório pode ficar atrás de um serviço da Web ou WCF; pode ser exposto ao AJAX ou a outros chamadores que não sabem a primeira coisa sobre o LINQ.
fonte
IQueryable
sem expor oIQueryable
próprio . Como você fará isso com a API incorporada?Realmente existe apenas uma resposta legítima: depende de como o repositório deve ser usado.
Em um extremo, seu repositório é um invólucro muito fino em torno de um DBContext, para que você possa injetar uma camada de testabilidade em um aplicativo orientado a banco de dados. Realmente, não há expectativa no mundo real de que isso possa ser usado de maneira desconectada sem um banco de dados compatível com LINQ por trás dele, porque seu aplicativo CRUD nunca precisará disso. Então, claro, por que não usar IQueryable? Eu provavelmente preferiria o IEnumarable, pois você obtém a maioria dos benefícios [leia-se: execução atrasada] e não parece tão sujo.
A menos que você esteja no extremo, tentarei tirar proveito do espírito do padrão de repositório e retornar coleções materializadas apropriadas que não impliquem uma conexão de banco de dados subjacente.
fonte
IEnumerable
, é importante observar que((IEnumerable<T>)query).LastOrDefault()
é muito diferente dequery.LastOrDefault()
(presumir quequery
seja umIQueryable
). Portanto, faz sentido retornarIEnumerable
se você repetir todos os elementos de qualquer maneira.NÃO, se você deseja fazer desenvolvimento / teste sem orientação, porque não há uma maneira fácil de criar um repositório simulado para testar sua businesslogic (= repository-consumer) isoladamente do banco de dados ou de outro provedor IQueryable.
fonte
Do ponto de vista da arquitetura pura, o IQueryable é uma abstração com vazamento. Como uma generalidade, fornece ao usuário muita energia. Dito isto, há lugares onde faz sentido. Se você estiver usando OData, o IQueryable torna extremamente fácil fornecer um ponto de extremidade que seja facilmente filtrável, classificável, agrupável ... etc. Na verdade, prefiro criar DTOs para os quais minhas entidades mapeiam e o IQueryable retorna o DTO. Dessa forma, pré-filtro meus dados e realmente só uso o método IQueryable para permitir a personalização dos resultados pelo cliente.
fonte
Eu já enfrentei essa escolha também.
Então, vamos resumir os lados positivo e negativo:
Positivos:
Negativos:
Eu recomendaria não usar essa abordagem. Em vez disso, considere criar várias especificações e implementar métodos de repositório, que podem consultar o banco de dados usando esses. O padrão de especificação é uma ótima maneira de encapsular um conjunto de condições.
fonte
Eu acho que expor o IQueryable no seu repositório é perfeitamente aceitável durante as fases iniciais do desenvolvimento. Existem ferramentas de interface do usuário que podem trabalhar diretamente com o IQueryable e lidar com classificação, filtragem, agrupamento etc.
Dito isto, acho que apenas expor crud no repositório e encerrar o dia é irresponsável. Ter operações úteis e auxiliares de consulta para consultas comuns permite centralizar a lógica de uma consulta enquanto expõe uma superfície de interface menor para os consumidores do repositório.
Nas primeiras iterações de um projeto, a exposição pública do IQueryable no repositório permite um crescimento mais rápido. Antes da primeira versão completa, eu recomendaria tornar a operação de consulta privada ou protegida e colocar as consultas selvagens sob o mesmo teto.
fonte
O que eu vi feito (e o que estou me implementando) é o seguinte:
O que isso permite que você faça é ter a flexibilidade de fazer uma
IQueryable.Where(a => a.SomeFilter)
consulta em seuconcreteRepo.GetAll(a => a.SomeFilter)
repositório, sem expor nenhum negócio LINQy.fonte