web-dev-qa-db-fra.com

Pour renvoyer IQueryable <T> ou ne pas renvoyer IQueryable <T>

J'ai une classe de référentiel qui enveloppe mon LINQ to SQL Data Context. La classe de référentiel est une classe de ligne métier qui contient toute la logique de la couche de données (et la mise en cache, etc.).

Voici ma v1 de mon interface repo.

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

Mais pour gérer la pagination pour FindAll, je me demande s'il faut ou non exposer IQueryable <ILocation> au lieu de IList pour simplifier l'interface dans des circonstances telles que la pagination.

Quels sont les avantages et les inconvénients d'exposer IQueryable à partir du référentiel de données?

Toute aide est fortement appréciée.

73
CVertex

Les avantages; composabilité:

  • les appelants peuvent ajouter des filtres
  • les appelants peuvent ajouter la pagination
  • les appelants peuvent ajouter un tri
  • etc

Les inconvénients; non-testabilité:

  • Votre référentiel n'est plus correctement testable unitaire; vous ne pouvez pas compter sur a: ça marche, b: ce que il fait;
    • l'appelant peut ajouter une fonction non traduisible (c'est-à-dire pas de mappage TSQL; pause à l'exécution)
    • l'appelant peut ajouter un filtre/tri qui le fait fonctionner comme un chien
  • Puisque les appelants attendent IQueryable<T> pour être composable, cela exclut les implémentations non composables - ou il vous oblige à écrire votre propre fournisseur de requêtes pour elles
  • cela signifie que vous ne pouvez pas optimiser/profiler le DAL

Pour la stabilité, j'ai pris pour pas exposer IQueryable<T> ou Expression<...> sur mes référentiels. Cela signifie que je sais comment le référentiel se comporte et que mes couches supérieures peuvent utiliser des simulations sans se soucier "est-ce que le référentiel actuel prend en charge cela?" (forcer les tests d'intégration).

J'utilise toujours IQueryable<T> etc à l'intérieur le référentiel - mais pas au-dessus de la limite. J'ai posté quelques plus de réflexions sur ce thème ici . Il est tout aussi simple de mettre des paramètres de pagination sur l'interface du référentiel. Vous pouvez même utiliser des méthodes d'extension (sur l'interface) pour ajouter des paramètres de pagination optionnel, afin que les classes concrètes n'aient qu'une seule méthode à implémenter, mais il peut y avoir 2 ou 3 surcharges disponibles pour l'appelant .

90
Marc Gravell

Comme mentionné dans la réponse précédente, exposer IQueryable donne accès aux appelants pour jouer avec IQueryable lui-même, ce qui est ou peut devenir dangereux.

La première responsabilité d'encapsuler la logique métier est de maintenir l'intégrité de votre base de données.

Vous pouvez continuer à exposer IList et modifier vos paramètres comme suit, c'est ainsi que nous faisons ...

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

si taille == -1 alors renvoie tout ...

Voie alternative ...

Si vous souhaitez toujours retourner IQueryable, vous pouvez renvoyer IQueryable de List dans vos fonctions .. par exemple ...

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

La première méthode a un avantage sur la mémoire, car vous retournerez moins de données au lieu de tout.

7
Akash Kava

Je recommande d'utiliser IEnumerable au lieu de IList, avec elle, vous aurez plus de flexibilité.

De cette façon, vous ne pourrez obtenir de Db que la partie des données que vous utiliserez vraiment sans travail supplémentaire effectué dans votre référentiel.

Échantillon:

// Repository
public interface IRepository
{
    IEnumerable<Location> GetLocations();
}

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

Ce qui est super propre et simple.

2