Os repositórios devem retornar IQueryable?

60

Tenho visto muitos projetos com repositórios que retornam instâncias de IQueryable . Isso permite que filtros adicionais e classificação possam ser executados no IQueryable por outro código, o que se traduz em SQL diferente sendo gerado. Estou curioso de onde esse padrão veio e se é uma boa ideia.

Minha maior preocupação é que um IQueryable seja uma promessa de acertar o banco de dados algum tempo depois, quando ele 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 enfrentei problemas com o MARS (Multiple Active Result Sets, conjuntos de resultados ativos múltiplos) no passado (especialmente ao usar transações) e essa abordagem parece que isso levaria a que isso acontecesse com mais frequência.

Sempre chamei AsEnumerable ou ToArray no final de cada uma das minhas expressões LINQ para garantir que o banco de dados seja atingido antes de deixar o código do repositório.

Eu estou querendo saber se retornar 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 construir um IQueryable ainda maior.

    
por Travis Parks 26.03.2013 / 21:57
fonte

9 respostas

42

A devolução do IQueryable oferecerá mais flexibilidade aos consumidores do repositório. Coloca a responsabilidade de restringir os resultados ao 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 nesta camada) - GetAllActiveItems, GetAllNonActiveItems, etc - para obter os dados desejados. Dependendo da sua preferência, isso pode ser bom ou ruim. Você irá (/ deve) precisar definir contratos comportamentais aos quais suas implementações aderem, mas aonde isso vai depender de você.

Assim, você poderia colocar a lógica de recuperação do lado de fora do repositório e deixá-lo ser usado, da maneira que o usuário desejar. Portanto, expor o IQueryable oferece mais flexibilidade e permite uma consulta eficiente, em oposição à filtragem na memória, etc., e poderia reduzir a necessidade de fazer uma tonelada de métodos de busca de dados específicos.

Por outro lado, agora você deu aos seus usuários uma espingarda. Eles podem fazer coisas que você pode não ter pretendido (uso excessivo de .include (), fazer pesadas consultas e fazer filtragem na memória em suas respectivas implementações, etc), o que basicamente ajudaria as camadas. e controles comportamentais, porque você deu acesso total.

Então, dependendo da equipe, da experiência deles, do tamanho do aplicativo, da disposição geral e da arquitetura ... isso depende: - \

    
por 26.03.2013 / 22:34
fonte
26

Expor IQueryable para interfaces públicas não é uma boa prática. A razão é fundamental: você não pode fornecer a realização do 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:

  1. O IQueryable é quase impossível de implementar. E atualmente não há solução adequada. Mesmo o Entity Framework tem várias UnsupportedExceptions .
  2. Hoje, os provedores Queryable têm grande dependência da infraestrutura. O Sharepoint tem sua própria implementação parcial e o provedor My SQL tem implementação parcial distinta.

Padrão de repositório nos dá uma representação clara por camadas e, mais importante, independência de realização. Assim, podemos mudar na fonte de dados do futuro.

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 prática ruim)

    
por 16.12.2013 / 20:40
fonte
18

Realisticamente, você tem três alternativas se quiser uma execução adiada:

  • Faça assim: exponha um IQueryable .
  • Implemente uma estrutura que expõe métodos específicos para filtros ou "perguntas" específicos. ( GetCustomersInAlaskaWithChildren , abaixo)
  • Implemente uma estrutura que expõe uma API de filtro / classificação / página strongmente tipada e constrói o IQueryable internamente.

Eu prefiro o terceiro (embora ajudei colegas a implementar o segundo também), mas obviamente há alguma configuração e manutenção envolvida. (T4 para o resgate!)

Editar : Para esclarecer a confusão em torno do que estou falando, considere o seguinte exemplo roubado de IQueryable pode matar seu cão , Roube sua esposa, mate sua vontade de viver, etc.

No cenário em que você expõe algo assim:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

a API da qual estou falando permitiria expor um método que permitiria que você expressasse 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 de repositório, por isso ainda aproveita a execução adiada. Depois de obter os resultados de volta, você ainda pode LINQ-to-objetos para o conteúdo do seu coração.

Outra vantagem de uma abordagem como essa é que, como são classes POCO, esse repositório pode viver por trás de um serviço da Web ou WCF; ele pode ser exposto ao AJAX ou a outros chamadores que não conhecem a primeira coisa sobre o LINQ.

    
por 26.03.2013 / 22:28
fonte
8

Existe apenas uma resposta legítima: depende de como o repositório deve ser usado.

Em um extremo, seu repositório é um wrapper muito fino em torno de um DBContext, para que você possa injetar um verniz de testabilidade em um aplicativo orientado a banco de dados. Não há realmente nenhuma expectativa do mundo real de que isso possa ser usado de maneira desconectada sem um banco de dados amigável do LINQ, porque o seu aplicativo CRUD não vai precisar disso. Então, claro, por que não usar o IQueryable? Eu provavelmente prefiro IEnumarable como você obtém a maioria dos benefícios [leia-se: atraso de execução] e não se sente tão sujo.

A menos que você esteja sentado nesse extremo, eu tentaria tirar proveito do espírito do padrão de repositório e retornar coleções materializadas apropriadas que não tenham uma implicação de uma conexão de banco de dados subjacente.

    
por 26.03.2013 / 22:23
fonte
5
 > Should Repositories return IQueryable?

NÃO se você deseja testar / testar testes porque não existe uma maneira fácil de criar um repositório simulado para testar seu businesslogic (= repository-consumer) isoladamente do banco de dados ou de outro provedor IQueryable.

    
por 21.01.2014 / 18:00
fonte
5

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 isso faz sentido. Se você estiver usando o OData, o IQueryable torna extremamente fácil fornecer um endpoint que é facilmente filtrável, classificável, agrupável ... etc. Na verdade, prefiro criar DTOs em que minhas entidades mapeiam e o IQueryable retorna o DTO. Dessa forma, eu pré-filtro meus dados e realmente só uso o método IQueryable para permitir a personalização do cliente dos resultados.

    
por 17.06.2015 / 21:42
fonte
4

Acho que expor o IQueryable no seu repositório é perfeitamente aceitável durante as fases iniciais de desenvolvimento. Existem ferramentas de interface do usuário que podem trabalhar diretamente com o IQueryable e tratar de classificação, filtragem, agrupamento, etc.

Dito isto, acho que apenas expor o lixo no repositório e chamá-lo por dia é irresponsável. Ter operações úteis e ajudantes de consulta para consultas comuns permite centralizar a lógica de uma consulta e, ao mesmo tempo, expor uma superfície de interface menor para os consumidores do repositório.

Nas primeiras iterações de um projeto, expor o IQueryable publicamente no repositório permite um crescimento mais rápido. Antes do primeiro lançamento completo, eu recomendaria tornar a operação de consulta privada ou protegida e colocar as consultas indisponíveis sob o mesmo teto.

    
por 26.03.2013 / 22:33
fonte
4

O que eu vi feito (e o que estou implementando) é o seguinte:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

O que isso permite que você faça é ter a flexibilidade de fazer uma consulta IQueryable.Where(a => a.SomeFilter) em seu repositório fazendo concreteRepo.GetAll(a => a.SomeFilter) sem expor nenhum negócio LINQy.

    
por 21.01.2014 / 17:03
fonte
4

Já enfrentei essa escolha também.

Então, vamos resumir lados positivos e negativos:

Positivos:

  • Flexibilidade. É definitivamente flexível e conveniente permitir que o lado do cliente construa consultas personalizadas com apenas um método de repositório

Negativos:

  • Untestable . (você estará realmente testando a implementação subjacente do IQueryable, que geralmente é o seu ORM. Mas você deve testar sua lógica)
  • Desfaz a responsabilidade . É impossível dizer o que esse método específico do seu repositório faz. Na verdade, é um método deus, que pode retornar qualquer coisa que você goste em todo o seu banco de dados
  • Inseguro . Sem restrições sobre o que pode ser consultado por este método de repositório, em alguns casos tais implementações podem ser inseguras do ponto de segurança.
  • Problemas de cache (ou, para ser genérico, qualquer problema de pré ou pós-processamento). Geralmente, não é prudente, por exemplo, armazenar em cache tudo em seu aplicativo. Você tentará armazenar em cache algo que cause carga significativa ao seu banco de dados. Mas como fazer isso, se você tiver apenas um método de repositório, esses clientes usam para emitir virtualmente qualquer consulta no banco de dados?
  • Problemas de registro . Registrar algo na camada do repositório fará com que você inspecione o IQueryable primeiro para verificar se essa chamada específica deve ser registrada ou não.

Eu recomendaria contra o uso dessa abordagem. Considere, em vez disso, criar várias Especificações e implementar métodos de repositório, que possam consultar o banco de dados usando-os. O padrão de especificação é uma ótima maneira de encapsular um conjunto de condições.

    
por 06.11.2015 / 15:54
fonte