Écriture de requêtes LINQ C# pour interroger des données

La plupart des requêtes de la documentation de présentation de Langage Integrated Query (LINQ) sont écrites à l’aide de la syntaxe de requête déclarative de LINQ. Toutefois, la syntaxe de requête doit être traduite en appels de méthode pour le Common Langage Runtime (CLR) .NET lorsque le code est compilé. Ces appels de méthode appellent les opérateurs de requête standard, qui ont des noms tels que Where, Select, GroupBy, Join, Max et Average. Vous pouvez les appeler directement en utilisant la syntaxe de méthode à la place de la syntaxe de requête.

La syntaxe de requête et la syntaxe de méthode sont identiques sémantiquement, mais la syntaxe de requête est souvent plus simple et plus facile à lire. Certaines requêtes doivent être exprimées en tant qu’appels de méthode. Par exemple, vous devez utiliser un appel de méthode pour exprimer une requête qui récupère le nombre d’éléments qui correspondent à une condition spécifiée. Vous devez également utiliser un appel de méthode pour une requête qui récupère dans une séquence source l’élément qui a la valeur maximale. En général, la documentation de référence des opérateurs de requête standard dans l’espace de noms System.Linq utilise la syntaxe de méthode. Vous devez vous familiariser avec l’utilisation de la syntaxe de méthode dans des requêtes et dans des expressions de requête elles-mêmes.

Méthodes d’extension d’opérateur de requête standard

L’exemple suivant présente une expression de requête simple et la requête sémantiquement équivalente écrite en tant que requête fondée sur une méthode.

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
    from num in numbers
    where num % 2 == 0
    orderby num
    select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)
{
    Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
    Console.Write(i + " ");
}

La sortie des deux exemples est identique. Vous pouvez voir que le type de la variable de requête est le même dans les deux formes : IEnumerable<T>.

Pour comprendre la requête basée sur une méthode, examinons-la de plus près. Du côté droit de l’expression, remarquez que la clause where est maintenant exprimée comme une méthode d’instance sur l’objet numbers qui a un type IEnumerable<int>. Si vous connaissez bien l’interface générique IEnumerable<T>, vous savez qu’elle n’a pas de méthode Where. Toutefois, si vous appelez la liste de saisie semi-automatique IntelliSense dans l’IDE de Visual Studio, vous ne voyez pas seulement une méthode Where, mais de nombreuses autres méthodes telles que Select, SelectMany, Join et Orderby. Ces méthodes implémentent les opérateurs de requête standard.

Screenshot showing all the standard query operators in Intellisense.

Bien que IEnumerable<T> semble inclure des méthodes supplémentaires, ce n’est pas le cas. Les opérateurs de requête standard sont implémentés en tant que méthodes d’extension. Les méthodes d’extension « étendent » un type existant. Elles peuvent être appelées comme s’il s’agissait de méthodes d’instance sur le type. Les opérateurs de requête standard étendent IEnumerable<T>, si bien que vous pouvez écrire numbers.Where(...).

Pour utiliser des méthodes d’extension, vous les placez dans l’étendue avec des directives using. Du point de vue de votre application, une méthode d’extension et une méthode d’instance normale sont identiques.

Pour plus d’informations sur les méthodes d’extension, consultez Méthodes d’extension. Pour plus d’informations sur les opérateurs de requête standard, consultez Présentation des opérateurs de requête standard (C#). Certains fournisseurs LINQ, tels que Entity Framework et LINQ to XML, implémentent leurs propres opérateurs de requête standard et méthodes d’extension supplémentaires pour d’autres types que IEnumerable<T>.

Expressions lambda

Dans l’exemple précédent, notez que l’expression conditionnelle (num % 2 == 0) est passée comme argument de ligne à la méthode Enumerable.Where : Where(num => num % 2 == 0). Cette expression inlined est appelée expression lambda. C’est un moyen pratique d’écrire du code qui devrait sinon être écrit sous une forme plus lourde. L’élément num situé à gauche de l’opérateur est la variable d’entrée qui correspond à num dans l’expression de requête. Le compilateur peut déduire le type de num, car il sait que numbers est un type IEnumerable<T> générique. Le corps de l’expression lambda est identique à l’expression dans la syntaxe de requête ou dans toute autre expression ou instruction C#. Il peut inclure des appels de méthode et d’autres logiques complexes. La valeur de retour est simplement le résultat de l’expression. Certaines requêtes peuvent être exprimées uniquement dans une syntaxe de méthode. Parmi elles, certaines nécessitent des expressions lambda. Les expressions lambda constituent un outil puissant et flexible dans votre boîte à outils LINQ.

Composabilité des requêtes

Dans l’exemple de code précédent, notez que la méthode Enumerable.OrderBy est appelée en utilisant l’opérateur point sur l’appel à Where. Where produit une séquence filtrée, puis Orderby trie la séquence produite par Where. Étant donné que les requêtes retournent un IEnumerable, vous les composez dans la syntaxe de méthode en chaînant les appels de méthode ensemble. Le compilateur effectue cette composition lorsque vous écrivez des requêtes en tirant parti de la syntaxe de requête. Étant donné qu’une variable de requête ne stocke pas les résultats de la requête, vous pouvez la modifier ou l’utiliser à tout moment comme base d’une nouvelle requête, même après son exécution.

Les exemples suivants montrent des requêtes LINQ simples en utilisant chaque approche répertoriée précédemment.

Remarque

Ces requêtes fonctionnent sur les collections simples en mémoire ; toutefois, la syntaxe de base est identique à celle utilisée dans LINQ to Entities et LINQ to XML.

Exemple – Syntaxe de requête

Vous écrivez la plupart des requêtes avec une la syntaxe de requête pour créer des expressions de requête. L’exemple suivant présente trois expressions de requête. La première expression de requête montre comment filtrer ou restreindre des résultats en appliquant des conditions avec une clause where. Tous les éléments de la séquence source dont la valeur est supérieure à 7 ou inférieure à 3 sont retournés. La deuxième expression montre comment classer les résultats retournés. La troisième expression montre comment regrouper des résultats en fonction d’une clé. Cette requête retourne deux groupes en fonction de la première lettre du mot.

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

Le type des requêtes est IEnumerable<T>. Toutes ces requêtes pourraient être écrites à l’aide de var, comme illustré dans l’exemple suivant :

var query = from num in numbers...

Dans chacun des exemples précédents, les requêtes ne s’exécutent pas réellement tant vous n’avez pas itéré la variable de requête dans une instruction foreach ou une autre instruction.

Exemple – Syntaxe de méthode

Certaines opérations de requête doivent être exprimées comme un appel de méthode. Les plus répandues de ces méthodes retournent des valeurs numériques singleton, telles que Sum, Max, Min, Average et ainsi de suite. Ces méthodes doivent toujours être appelées en dernier dans toutes les requêtes, car elles retournent une valeur unique et ne peuvent pas servir de source pour une opération de requête supplémentaire. L’exemple suivant présente un appel de méthode dans une expression de requête :

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Si la méthode a des paramètres System.Action ou System.Func<TResult>, ces arguments sont fournis sous la forme d’une expression lambda, comme dans l’exemple suivant :

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Dans les requêtes précédentes, seule Requête #4 s’exécute immédiatement car elle retourne une valeur unique, et non pas une collection IEnumerable<T> générique. La méthode elle-même utilise foreach ou du code similaire pour calculer sa valeur.

Chacune des requêtes précédentes peut être écrite en utilisant des types implicites avec `var``, comme dans l’exemple suivant :

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Exemple – Syntaxe de méthode et de requête mixte

Cet exemple montre comment utiliser la syntaxe de méthode sur les résultats d’une clause de requête. Encadrez simplement l’expression de requête entre parenthèses, puis appliquez l’opérateur point et appelez la méthode. Dans l’exemple suivant, la requête 7 retourne les nombres dont la valeur est comprise entre 3 et 7. Cependant, il est généralement préférable d’utiliser une deuxième variable pour stocker le résultat de l’appel de méthode. De cette manière, il est moins probable que la requête ne soit confondue avec les résultats de la requête.

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

Comme la requête 7 retourne une valeur unique et non une collection, la requête s’exécute immédiatement.

La requête précédente peut être écrite en utilisant des types implicites avec var, comme suit :

var numCount = (from num in numbers...

Elle peut être écrite dans la syntaxe de méthode comme suit :

var numCount = numbers.Count(n => n is > 3 and < 7);

Elle peut être écrite en utilisant des types explicites, comme suit :

int numCount = numbers.Count(n => n is > 3 and < 7);

Voir aussi