web-dev-qa-db-fra.com

Différences entre IQueryable, List, IEnumerator?

Je me demande quelle est la différence entre IQueryable, List, IEnumerator et quand dois-je utiliser chacun d'eux?

Par exemple, lorsque j'utilise Linq to SQL, je ferais quelque chose comme ceci:

public List<User> GetUsers()
{
   return db.User.where(/* some query here */).ToList();
}

Maintenant, je me demande si je devrais utiliser IQueryable à la place. Je ne suis pas sûr des avantages de l'utiliser sur la liste.

61
chobo2

IQueryable<T> Est destiné à permettre à un fournisseur de requêtes (par exemple, un ORM comme LINQ to SQL ou Entity Framework) d'utiliser les expressions contenues dans une requête pour traduire la demande dans un autre format. En d'autres termes, LINQ-to-SQL examine les propriétés des entités que vous utilisez avec les comparaisons que vous faites et crée en fait une instruction SQL pour exprimer (espérons-le) une demande équivalente.

IEnumerable<T> Est plus générique que IQueryable<T> (Bien que toutes les instances de IQueryable<T> Implémentent IEnumerable<T>) Et ne définit qu'une séquence. Cependant, il existe des méthodes d'extension disponibles dans la classe Enumerable qui définissent certains opérateurs de type requête sur cette interface et utilisent du code ordinaire pour évaluer ces conditions.

List<T> N'est qu'un format de sortie et, bien qu'il implémente IEnumerable<T>, Il n'est pas directement lié à l'interrogation.

En d'autres termes, lorsque vous utilisez IQueryable<T>, Vous définissez une expression qui est traduite en autre chose. Même si vous écrivez du code, ce code n'est jamais exécuté , il est seulement inspecté et transformé en autre chose, comme une requête SQL réelle. Pour cette raison, seules certaines choses sont valides dans ces expressions. Par exemple, vous ne pouvez pas appeler une fonction ordinaire que vous définissez à partir de ces expressions car LINQ-to-SQL ne sait pas comment transformer votre appel en une instruction SQL. La plupart de ces restrictions ne sont malheureusement évaluées qu'au moment de l'exécution.

Lorsque vous utilisez IEnumerable<T> Pour les requêtes, vous utilisez LINQ-to-Objects, ce qui signifie que vous écrivez le code réel qui est utilisé pour évaluer votre requête ou transformer les résultats, donc il n'y a, en général, aucun restrictions sur ce que vous pouvez faire. Vous pouvez appeler librement d'autres fonctions à partir de ces expressions.

Avec LINQ to SQL

En allant de pair avec la distinction ci-dessus, il est également important de garder à l'esprit comment cela fonctionne dans la pratique. Lorsque vous écrivez une requête sur une classe de contexte de données dans LINQ to SQL, elle produit un IQueryable<T>. Quoi que vous fassiez contre le IQueryable<T> Lui-même va être transformé en SQL, donc votre filtrage et votre transformation seront effectués sur le serveur. Quoi que vous fassiez contre cela en tant que IEnumerable<T>, cela se fera au niveau de l'application. Parfois, cela est souhaitable (dans le cas où vous devez utiliser un code côté client, par exemple), mais dans de nombreux cas, cela n'est pas intentionnel.

Par exemple, si j'avais un contexte avec une propriété Customers représentant une table Customer et que chaque client a une colonne CustomerId, examinons deux façons de faire cette requête:

var query = (from c in db.Customers where c.CustomerId == 5 select c).First();

Cela produira du SQL qui interroge la base de données pour l'enregistrement Customer avec un CustomerId égal à 5. Quelque chose comme:

select CustomerId, FirstName, LastName from Customer where CustomerId = 5

Maintenant, que se passe-t-il si nous transformons Customers en un IEnumerable<Customer> En utilisant la méthode d'extension AsEnumerable()?

var query = (from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c).First();

Ce simple changement a de graves conséquences. Puisque nous transformons Customers en un IEnumerable<Customer>, Cela ramènera la table entière et la filtrera du côté client (enfin, à proprement parler, cela ramènera chaque ligne de la table jusqu'à ce qu'il en rencontre un qui corresponde aux critères , mais le point est le même).

ToList ()

Jusqu'à présent, nous n'avons parlé que de IQueryable et IEnumerable. En effet, ce sont des interfaces complémentaires similaires. Dans les deux cas, vous définissez une requête ; c'est-à-dire que vous définissez pour trouver les données, quoi filtres à appliquer et quelles données renvoyer. Ces deux sont des requêtes

query = from c in db.Customers where c.CustomerId == 5 select c;
query = from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c;

Comme nous en avons parlé, la première requête utilise IQueryable et la seconde utilise IEnumerable. Dans les deux cas, cependant, il s'agit simplement d'une requête . La définition de la requête ne fait rien contre la source de données. La requête est en fait exécutée lorsque le code commence à parcourir la liste. Cela peut se produire de plusieurs façons; une boucle foreach, appelant ToList(), etc.

La requête est exécutée la première et chaque fois qu'elle est itérée. Si vous deviez appeler ToList() sur query deux fois, vous vous retrouveriez avec deux listes avec des objets complètement distincts. Ils pourraient contenir les mêmes données, mais ce seraient des références différentes.

Modifier après les commentaires

Je veux juste être clair sur la distinction entre quand les choses sont faites côté client et quand elles sont faites côté serveur. Si vous faites référence à un IQueryable<T> En tant que IEnumerable<T>, uniquement la requête effectuée après c'est un IEnumerable<T> qui sera fait côté client. Par exemple, disons que j'ai cette table et un contexte LINQ-to-SQL:

Customer
-----------
CustomerId
FirstName
LastName

Je construis d'abord une requête basée sur FirstName. Cela crée un IQueryable<Customer>:

var query = from c in db.Customers where c.FirstName.StartsWith("Ad") select c;

Maintenant, je passe cette requête à une fonction qui prend un IEnumerable<Customer> Et effectue un filtrage basé sur LastName:

public void DoStuff(IEnumerable<Customer> customers)
{
    foreach(var cust in from c in customers where c.LastName.StartsWith("Ro"))
    {
        Console.WriteLine(cust.CustomerId);
    }
}

Nous avons fait une deuxième requête ici, mais cela se fait sur un IEnumerable<Customer>. Ce qui va se passer ici, c'est que la première requête sera évaluée, en exécutant ce SQL:

select CustomerId, FirstName, LastName from Customer where FirstName like 'Ad%'

Nous allons donc ramener tous ceux qui commencent par FirstName par "Ad". Notez qu'il n'y a rien ici à propos de LastName. C'est parce qu'il est filtré côté client.

Une fois qu'il ramène ces résultats, le programme répète ensuite les résultats et ne délivre que les enregistrements dont LastName commence par "Ro". L'inconvénient est que nous avons ramené des données - à savoir, toutes les lignes dont LastName ne commence pas par "Ro" - qui aurait pu être filtré sur le serveur.

107
Adam Robinson

IQueryable<T>: Accès à la base de données des résumés, prend en charge l'évaluation paresseuse des requêtes
List<T>: Une collection d'entrées. Aucun support de l'évaluation paresseuse
IEnumerator<T>: Offre la possibilité d'itérer sur et IEnumerable<T> (Qui sont à la fois IQueryable<T> Et List<T>)

Le problème avec ce code est assez simple - il exécute toujours la requête lorsqu'elle est appelée. Si vous deviez renvoyer db.User.Where(...) à la place (qui est un IQueryable<T>), Vous retiendriez l'évaluation de la requête jusqu'à ce qu'elle soit réellement nécessaire (répétée). De plus, si l'utilisateur de cette méthode doit spécifier d'autres prédicats, ceux-ci doivent également être exécutés dans la base de données, ce qui le rend beaucoup plus rapide.

8
Femaref

Utilisez iList ou List<item> lorsque vous voulez une collection fortement typée d'une certaine entité.

Et utilisez Iqueryable et Ienumurator lorsque vous souhaitez obtenir des données stupides en tant que collection d'objets, elles reviendront en tant que collection de type lâche et aucune restriction ne sera appliquée.

Je préfère utiliser List<type> parce que l'utilisation d'un habillage de liste et de cast dans une collection de type fortement mon jeu de résultats.

En outre, l'utilisation d'une liste vous donnera la possibilité d'ajouter, de trier et de convertir un calque en tableau, Ienumurator ou en tant que requête.

0
Mahmoud Sayed