Para retornar IQueryable ou não retornar IQueryable

Eu tenho uma class de repository que envolve meu LINQ para contexto de dados SQL. A class de repository é uma class de linha de negócios que contém toda a lógica da camada de dados (e o armazenamento em cache e outros).

Aqui está a minha v1 da minha interface repo.

public interface ILocationRepository { IList FindAll(); IList FindForState(State state); IList FindForPostCode(string postCode); } 

Mas para lidar com paginação para FindAll, estou debatendo se expor ou não IQueryable em vez de IList para simplificar a interface para circunstâncias como paginação.

Quais são os prós e contras de expor o IQueryable do repo de dados?

Qualquer ajuda é muito apreciada.

Os prós; composibilidade:

  • chamadores podem adicionar filtros
  • chamadores podem adicionar paginação
  • chamadores podem adicionar sorting
  • etc

Os contras; não testabilidade:

  • Seu repository não é mais adequadamente testável em unidade; você não pode confiar em: funciona, b: o que faz;
    • o chamador poderia adicionar uma function não traduzível (ou seja, nenhum mapeamento TSQL; quebras em tempo de execução)
    • o chamador poderia adicionar um filtro / tipo que o faz funcionar como um cão
  • Como os chamadores esperam que o IQueryable seja composable, ele exclui implementações não compostas – ou obriga você a escrever seu próprio provedor de consulta para eles
  • isso significa que você não pode otimizar / perfilar o DAL

Por questões de estabilidade, tomei para não expor IQueryable ou Expression<...> em meus repositorys. Isso significa que eu sei como o repository se comporta, e minhas camadas superiores podem usar mocks sem se preocupar “o repository atual suporta isso?” (forçando testes de integração).

Eu ainda uso IQueryable etc dentro do repository – mas não acima do limite. Eu postei mais alguns pensamentos sobre este tema aqui . É tão fácil colocar parâmetros de paginação na interface do repository. Você pode até mesmo usar methods de extensão (na interface) para adicionar parâmetros de paginação opcionais , para que as classs concretas tenham apenas 1 método para implementar, mas pode haver 2 ou 3 sobrecargas disponíveis para o chamador.

Como mencionado pela resposta anterior, a exposição do IQueryable dá access aos chamadores para jogar com o próprio IQueryable, o que é ou pode se tornar perigoso.

Encapsular a primeira responsabilidade da lógica de negócios é manter a integridade do database.

Você pode continuar expondo IList e pode alterar seus parâmetros da seguinte forma, é assim que estamos fazendo …

 public interface ILocationRepository { IList FindAll(int start, int size); IList FindForState(State state, int start, int size); IList FindForPostCode(string postCode, int start, int size); } 

se tamanho == -1, retorne tudo …

Caminho alternativo…

Se você ainda quiser retornar o IQueryable, poderá retornar IQueryable of List dentro de suas funções … por exemplo …

 public class MyRepository { IQueryable FindAll() { List myLocations = ....; return myLocations.AsQueryable; // here Query can only be applied on this // subset, not directly to the database } } 

O primeiro método tem uma vantagem sobre a memory, porque você retornará menos dados em vez de todos.

Eu recomendo usar IEnumerable vez de IList , com isso você terá mais flexibilidade.

Desta forma você será capaz de obter do Db apenas aquela parte dos dados que você realmente usará sem trabalho extra feito em seu repository.

Amostra:

 // Repository public interface IRepository { IEnumerable GetLocations(); } // Controller public ActionResult Locations(int? page) { return View(repository.GetLocations().AsPagination(page ?? 1, 10); } 

Qual é super limpo e simples.