Présentation des opérateurs de requête standard (C#)

Les opérateurs de requête standard sont les méthodes qui forment le modèle de requête LINQ. La plupart de ces méthodes fonctionnent sur des séquences, où une séquence est un objet dont le type implémente l’interface IEnumerable<T> ou l’interface IQueryable<T>. Les opérateurs de requête standard fournissent des fonctionnalités de requête, dont notamment le filtrage, la projection, l’agrégation, le tri, etc.

Il existe deux ensembles d’opérateurs de requête standard LINQ : l’un opérant sur des objets de type IEnumerable<T> et l’autre sur des objets de type IQueryable<T>. Les méthodes qui composent chaque ensemble sont des membres statiques des classes Enumerable et Queryable, respectivement. Elles sont définies en tant que méthodes d’extension du type sur lequel elles opèrent. Les méthodes d’extension peuvent être appelées à l’aide de la syntaxe de méthode statique ou de la syntaxe de méthode d’instance.

De plus, plusieurs méthodes d’opérateur de requête standard fonctionnent sur des types autres que ceux basés sur IEnumerable<T> ou IQueryable<T>. Le type Enumerable définit deux de ces méthodes qui fonctionnent toutes deux sur les objets de type IEnumerable. Ces méthodes, Cast<TResult>(IEnumerable) et OfType<TResult>(IEnumerable), vous permettent d’autoriser l’interrogation d’une collection non paramétrée, ou non générique, dans le modèle LINQ. Pour cela, elles créent une collection fortement typée d’objets. La classe Queryable définit deux méthodes similaires, Cast<TResult>(IQueryable) et OfType<TResult>(IQueryable), qui opèrent sur les objets de type IQueryable.

Les opérateurs de requête standard diffèrent dans le déroulement de leur exécution, selon qu’ils retournent une valeur singleton ou une séquence de valeurs. Les méthodes qui retournent une valeur de singleton (par exemple, Average et Sum) s’exécutent immédiatement. Les méthodes qui retournent une séquence diffèrent l’exécution de la requête et retournent un objet énumérable.

Pour les méthodes qui opèrent sur des collections en mémoire, c’est-à-dire des méthodes qui étendent IEnumerable<T>, l’objet énumérable retourné capture les arguments passés à la méthode. Lorsque cet objet est énuméré, la logique de l’opérateur de requête est employée et les résultats de requête sont retournés.

En revanche, les méthodes qui étendent IQueryable<T> n’implémentent aucun comportement d’interrogation. Elles créent une arborescence d’expressions représentant la requête à effectuer. Le traitement des requêtes est géré par l’objet IQueryable<T> source.

Les appels aux méthodes de requête peuvent être chaînés dans une requête unique, ce qui permet de rendre les requêtes arbitrairement complexes.

L’exemple de code suivant illustre l’utilisation des opérateurs de requête standard pour obtenir des informations sur une séquence.

string sentence = "the quick brown fox jumps over the lazy dog";  
// Split the string into individual words to create a collection.  
string[] words = sentence.Split(' ');  
  
// Using query expression syntax.  
var query = from word in words  
            group word.ToUpper() by word.Length into gr  
            orderby gr.Key  
            select new { Length = gr.Key, Words = gr };  
  
// Using method-based query syntax.  
var query2 = words.  
    GroupBy(w => w.Length, w => w.ToUpper()).  
    Select(g => new { Length = g.Key, Words = g }).  
    OrderBy(o => o.Length);  
  
foreach (var obj in query)  
{  
    Console.WriteLine("Words of length {0}:", obj.Length);  
    foreach (string word in obj.Words)  
        Console.WriteLine(word);  
}  
  
// This code example produces the following output:  
//  
// Words of length 3:  
// THE  
// FOX  
// THE  
// DOG  
// Words of length 4:  
// OVER  
// LAZY  
// Words of length 5:  
// QUICK  
// BROWN  
// JUMPS

Syntaxe d’expression de requête

Certains des opérateurs de requête standard les plus courants ont une syntaxe de mots clés dédiée des langages C# et Visual Basic, qui permet de les appeler dans le cadre d’une expression de requête. Pour plus d’informations sur les opérateurs de requête standard qui ont des mots clés dédiés et sur leurs syntaxes correspondantes, consultez Syntaxe des expressions de requête pour les opérateurs de requête standard (C#).

Extension des opérateurs de requête standard

Vous pouvez augmenter l’ensemble d’opérateurs de requête standard en créant des méthodes spécifiques au domaine appropriées pour votre domaine ou technologie cible. Vous pouvez également remplacer les opérateurs de requête standard par vos propres implémentations qui fournissent des services supplémentaires, tels que l’évaluation à distance, la traduction des requêtes et l’optimisation. Pour obtenir un exemple, consultez AsEnumerable.

Obtenir une source de données

Dans une requête LINQ, la première étape consiste à spécifier la source de données. Dans C#, comme dans la plupart des langages de programmation, une variable doit être déclarée avant de pouvoir être utilisée. Dans une requête LINQ, la clause from apparaît en premier pour introduire la source de données (customers) et la variable de portée (cust).

//queryAllCustomers is an IEnumerable<Customer>
var queryAllCustomers = from cust in customers
                        select cust;

La variable de portée est similaire à la variable d’itération dans une boucle foreach, à la différence qu’aucune itération réelle ne se produit dans une expression de requête. Quand la requête est exécutée, la variable de portée sert de référence à chaque élément consécutif dans customers. Comme le compilateur déduit le type de cust, vous n’avez pas à le spécifier explicitement. Des variables de portée supplémentaires peuvent être introduites par une clause let. Pour plus d’informations, consultez let, clause.

Notes

Pour les sources de données non génériques telles que ArrayList, la variable de portée doit être explicitement typée. Pour plus d’informations, consultez Guide pratique pour interroger un ArrayList avec LINQ (C#) et from, clause.

Filtrage

L'opération de requête la plus courante est probablement l'application d'un filtre sous forme d'expression booléenne. Du fait du filtre, la requête retourne uniquement les éléments pour lesquels l’expression a la valeur true. Le résultat est produit à l'aide de la clause where. En effet, le filtre spécifie les éléments à exclure de la séquence source. Dans l’exemple suivant, seuls les clients (customers) qui ont une adresse à Londres sont retournés.

var queryLondonCustomers = from cust in customers
                           where cust.City == "London"
                           select cust;

Vous pouvez utiliser les opérateurs logiques C# AND et OR habituels pour appliquer le nombre d’expressions de filtre nécessaire dans la clause where. Par exemple, pour retourner uniquement les clients situés à « Londres » ET (AND) dont le nom est « Devon », vous devez écrire le code suivant :

where cust.City == "London" && cust.Name == "Devon"

Pour retourner les clients situés à Londres ou à Paris, vous devez écrire le code suivant :

where cust.City == "London" || cust.City == "Paris"

Pour plus d’informations, consultez where, clause.

Classement

Il est souvent utile de trier les données retournées. La clause orderby permet de trier les éléments de la séquence retournée en fonction du comparateur par défaut pour le type qui est trié. Par exemple, la requête suivante peut être étendue pour trier les résultats selon la propriété Name. Comme Name est une chaîne, le comparateur par défaut effectue un tri alphabétique de A à Z.

var queryLondonCustomers3 =
    from cust in customers
    where cust.City == "London"
    orderby cust.Name ascending
    select cust;

Pour trier les résultats dans l’ordre inverse, de Z à A, utilisez la clause orderby…descending.

Pour plus d’informations, consultez orderby, clause.

Regroupement

La clause group vous permet de grouper vos résultats selon une clé que vous spécifiez. Par exemple, vous pouvez spécifier un regroupement des résultats par ville (City) pour que tous les clients de Londres ou Paris soient dans des groupes individuels. Dans ce cas, utilisez la clé cust.City.

// queryCustomersByCity is an IEnumerable<IGrouping<string, Customer>>
  var queryCustomersByCity =
      from cust in customers
      group cust by cust.City;

  // customerGroup is an IGrouping<string, Customer>
  foreach (var customerGroup in queryCustomersByCity)
  {
      Console.WriteLine(customerGroup.Key);
      foreach (Customer customer in customerGroup)
      {
          Console.WriteLine("    {0}", customer.Name);
      }
  }

Quand vous terminez une requête avec une clause group, les résultats sont présentés sous la forme d’une liste de listes. Chaque élément de la liste est un objet qui a un membre Key et une liste d’éléments qui sont regroupés sous cette clé. Quand vous itérez une requête qui produit une séquence de groupes, vous devez utiliser une boucle foreach imbriquée. La boucle externe itère chaque groupe et la boucle interne itère les membres de chaque groupe.

Si vous devez faire référence aux résultats d’une opération de regroupement, utilisez le mot clé into pour créer un identificateur susceptible d’être interrogé ultérieurement. La requête suivante retourne uniquement les groupes qui contiennent plus de deux clients :

// custQuery is an IEnumerable<IGrouping<string, Customer>>
var custQuery =
    from cust in customers
    group cust by cust.City into custGroup
    where custGroup.Count() > 2
    orderby custGroup.Key
    select custGroup;

Pour plus d’informations, consultez group, clause.

Jonction

Les opérations de jointure créent des associations entre les séquences qui ne sont pas explicitement modélisées dans les sources de données. Par exemple, effectuez une jointure pour rechercher tous les clients et distributeurs qui se trouvent au même endroit. Dans LINQ, la clause join fonctionne toujours par rapport à des collections d’objets plutôt que directement par rapport à des tables de base de données.

var innerJoinQuery =
    from cust in customers
    join dist in distributors on cust.City equals dist.City
    select new { CustomerName = cust.Name, DistributorName = dist.Name };

Dans LINQ, vous n’avez pas à utiliser join aussi souvent que vous le faites dans SQL, car les clés étrangères dans LINQ sont représentées dans le modèle objet comme des propriétés qui contiennent une collection d’éléments. Par exemple, un objet Customer contient une collection d’objets Order. Au lieu d’effectuer une jointure, accédez aux commandes en utilisant la notation par points :

from order in Customer.Orders...  

Pour plus d’informations, consultez join, clause.

Sélection (projections)

La clause select produit les résultats de la requête et spécifie la « forme » ou le type de chaque élément retourné. Par exemple, spécifiez si les résultats doivent contenir des objets Customer complets, un seul membre, un sous-ensemble de membres ou un type de résultat complètement différent basé sur un calcul ou une création d’objet. Quand la clause select produit autre chose qu’une copie de l’élément source, l’opération est appelée projection. L’utilisation de projections pour transformer des données est une fonctionnalité puissante des expressions de requête LINQ. Pour plus d’informations, consultez Transformations de données avec LINQ (C#) et select, clause.

Tableau de syntaxe des expressions de requête

Le tableau ci-dessous répertorie les opérateurs de requête standard qui comportent des clauses d’expression de requête équivalentes.

Méthode Syntaxe d'expression de requête C#
Cast Utilisez une variable de portée explicitement typée, par exemple :

from int i in numbers

(Pour plus d’informations, consultez from, clause.)
GroupBy group … by

-ou-

group … by … into …

(Pour plus d’informations, consultez group, clause.)
GroupJoin<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,IEnumerable<TInner>, TResult>) join … in … on … equals … into …

(Pour plus d’informations, consultez join, clause.)
Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,TInner,TResult>) join … in … on … equals …

(Pour plus d’informations, consultez join, clause.)
OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby

(Pour plus d’informations, consultez orderby, clause.)
OrderByDescending<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby … descending

(Pour plus d’informations, consultez orderby, clause.)
Select select

(Pour plus d’informations, consultez select, clause.)
SelectMany Plusieurs clauses from.

(Pour plus d’informations, consultez from, clause.)
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, …

(Pour plus d’informations, consultez orderby, clause.)
ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, … descending

(Pour plus d’informations, consultez orderby, clause.)
Where where

(Pour plus d’informations, consultez where, clause.)

Voir aussi