Partager via


Vue d’ensemble des opérateurs de requête standard

Les opérateurs de requête standard sont les mots clés et méthodes qui forment le modèle LINQ. Le langage C# définit des mots clés de requête LINQ que vous utilisez pour l’expression de requête la plus courante. Le compilateur traduit des expressions à l’aide de ces mots clés en appels de méthode équivalents. Les deux formes sont synonymes. Les autres méthodes qui font partie de l’espace System.Linq de noms n’ont pas de mots clés de requête équivalents. Dans ce cas, vous devez utiliser la syntaxe de méthode. Cette section couvre tous les mots clés de l’opérateur de requête. Le runtime et d’autres packages NuGet ajoutent d’autres méthodes conçues pour fonctionner avec des requêtes LINQ chaque version. Les méthodes les plus courantes, y compris celles qui ont des équivalents de mot clé de requête, sont abordées dans cette section. Pour obtenir la liste complète des méthodes de requête prises en charge par le runtime .NET, consultez la documentation de l’API System.Linq.Enumerable . Outre les méthodes décrites ici, cette classe contient des méthodes pour concaténer des sources de données, en calculant une valeur unique à partir d’une source de données, telle qu’une somme, une moyenne ou une autre valeur.

Importante

Ces exemples utilisent une source de données System.Collections.Generic.IEnumerable<T>. Les sources de données basées sur System.Linq.IQueryProvider utilisent des sources de données System.Linq.IQueryable<T> et des arborescences d’expressions. Les arborescences d’expressions présentent des limitations sur la syntaxe C# autorisée. De plus, chaque source de données IQueryProvider, telle que EF Core peut imposer des restrictions supplémentaires. Consultez la documentation de votre source de données.

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 offrent des fonctionnalités de requête, notamment le filtrage, la projection, l’agrégation, le tri et bien plus encore. Les méthodes qui composent chaque ensemble sont des membres statiques des classes Enumerable et Queryable, respectivement. Ils sont définis en tant que méthodes d’extension du type sur lequel ils opèrent.

La distinction entre IEnumerable<T> et IQueryable<T> séquences détermine la façon dont la requête est exécutée au moment de l’exécution.

Pour 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 utilisée et les résultats de la requête sont retournés.

Pour IQueryable<T>, la requête est traduite en arborescence d’expressions. L’arborescence d’expressions peut être traduite en requête native lorsque la source de données peut optimiser la requête. Les bibliothèques telles que Entity Framework traduisent des requêtes LINQ en requêtes SQL natives qui s’exécutent sur la base de données.

L’exemple de code suivant montre comment les opérateurs de requête standard peuvent être utilisés 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 {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

Dans la mesure du possible, les requêtes de cette section utilisent une séquence de mots ou de nombres comme source d’entrée. Pour les requêtes où des relations plus complexes entre les objets sont utilisées, les sources suivantes qui modélisent une école sont utilisées :

public enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

public class Student
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required int ID { get; init; }

    public required GradeLevel Year { get; init; }
    public required List<int> Scores { get; init; }

    public required int DepartmentID { get; init; }
}

public class Teacher
{
    public required string First { get; init; }
    public required string Last { get; init; }
    public required int ID { get; init; }
    public required string City { get; init; }
}

public class Department
{
    public required string Name { get; init; }
    public int ID { get; init; }

    public required int TeacherID { get; init; }
}

Chaque Student a un niveau scolaire, un département principal et une série de notes. Un Teacher a également une propriété City qui identifie le campus où l’enseignant donne des cours. Un Department a un nom, et une référence à un Teacher qui est responsable du département.

Vous trouverez le jeu de données dans le référentiel source.

Types d’opérateurs de requête

Les opérateurs de requête standard diffèrent dans le minutage de leur exécution, selon qu’ils retournent une valeur singleton ou une séquence de valeurs. Ces méthodes qui retournent une valeur singleton (par exemple Average et Sum) s’exécutent immédiatement. Méthodes qui retournent une séquence reportent l’exécution de la requête et retournent un objet énumérable. Vous pouvez utiliser la séquence de sortie d’une requête comme séquence d’entrée pour une autre requête. Les appels aux méthodes de requête peuvent être chaînés dans une requête, ce qui permet aux requêtes de devenir arbitrairement complexes.

Opérateurs de requête

Dans une requête LINQ, la première étape consiste à spécifier la source de données. Dans une requête LINQ, la from clause est d’abord disponible pour introduire la source de données (students) et la variable de plage (student).

//queryAllStudents is an IEnumerable<Student>
var queryAllStudents = from student in students
                        select student;

La variable de plage est semblable à la variable d’itération dans une foreach boucle, sauf qu’aucune itération réelle ne se produit dans une expression de requête. Lorsque la requête est exécutée, la variable de plage sert de référence à chaque élément successif dans students. Étant donné que le compilateur peut déduire le type de student, vous n'avez pas besoin de le spécifier explicitement. Vous pouvez introduire d’autres variables de plage dans une let clause. Pour plus d’informations, consultez let, clause.

Remarque

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

Une fois que vous avez obtenu une source de données, vous pouvez effectuer n’importe quel nombre d’opérations sur cette source de données :

Table de syntaxe d’expression de requête

Le tableau suivant répertorie les opérateurs de requête standard qui ont des clauses d’expression de requête équivalentes.

Méthode Syntaxe d’expression de requête C#
Cast Utilisez une variable de plage typée explicitement :

from int i in numbers

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

- ou -

group … by … into …

(Pour plus d’informations, consultez la clause de groupe.)
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, voir 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 la clause where.)

Transformations de données avec LINQ

Language-Integrated Query (LINQ) ne concerne pas seulement la récupération des données. Il s’agit également d’un outil puissant pour transformer des données. À l’aide d’une requête LINQ, vous pouvez utiliser une séquence source comme entrée et la modifier de plusieurs façons pour créer une séquence de sortie. Vous pouvez modifier la séquence elle-même sans modifier les éléments eux-mêmes en triant et en regroupant. Mais la fonctionnalité la plus puissante des requêtes LINQ est peut-être la possibilité de créer de nouveaux types. La clause select crée un élément de sortie à partir d’un élément d’entrée. Vous l’utilisez pour transformer un élément d’entrée en élément de sortie :

  • Fusionnez plusieurs séquences d’entrée en une seule séquence de sortie qui a un nouveau type.
  • Créez des séquences de sortie dont les éléments se composent d’une ou plusieurs propriétés de chaque élément dans la séquence source.
  • Créez des séquences de sortie dont les éléments sont constitués des résultats des opérations effectuées sur les données sources.
  • Créez des séquences de sortie dans un autre format. Par exemple, vous pouvez transformer des données à partir de lignes SQL ou de fichiers texte en XML.

Ces transformations peuvent être combinées de différentes manières dans la même requête. En outre, la séquence de sortie d’une requête peut être utilisée comme séquence d’entrée pour une nouvelle requête. L’exemple suivant transforme les objets d’une structure de données en mémoire en éléments XML.


// Create the query.
var studentsToXML = new XElement("Root",
    from student in students
    let scores = string.Join(",", student.Scores)
    select new XElement("student",
                new XElement("First", student.FirstName),
                new XElement("Last", student.LastName),
                new XElement("Scores", scores)
            ) // end "student"
        ); // end "Root"

// Execute the query.
Console.WriteLine(studentsToXML);

Le code produit la sortie XML suivante :

<Root>
  <student>
    <First>Svetlana</First>
    <Last>Omelchenko</Last>
    <Scores>97,90,73,54</Scores>
  </student>
  <student>
    <First>Claire</First>
    <Last>O'Donnell</Last>
    <Scores>56,78,95,95</Scores>
  </student>
  ...
  <student>
    <First>Max</First>
    <Last>Lindgren</Last>
    <Scores>86,88,96,63</Scores>
  </student>
  <student>
    <First>Arina</First>
    <Last>Ivanova</Last>
    <Scores>93,63,70,80</Scores>
  </student>
</Root>

Pour plus d’informations, consultez Création d’arborescences XML en C# (LINQ to XML).

Vous pouvez utiliser les résultats d’une requête comme source de données pour une requête ultérieure. Cet exemple montre comment classer les résultats d’une opération de jointure. Cette requête crée une jointure groupée, puis trie les groupes en fonction de l’élément de catégorie, qui est encore dans la portée. Dans l’initialiseur de type anonyme, une sous-requête commande tous les éléments correspondants de la séquence de produits.

var orderedQuery = from department in departments
                   join student in students on department.ID equals student.DepartmentID into studentGroup
                   orderby department.Name
                   select new
                   {
                       DepartmentName = department.Name,
                       Students = from student in studentGroup
                                  orderby student.LastName
                                    select student
                   };

foreach (var departmentList in orderedQuery)
{
    Console.WriteLine(departmentList.DepartmentName);
    foreach (var student in departmentList.Students)
    {
        Console.WriteLine($"  {student.LastName,-10} {student.FirstName,-10}");
    }
}
/* Output:
Chemistry
  Balzan     Josephine
  Fakhouri   Fadi
  Popov      Innocenty
  Seleznyova Sofiya
  Vella      Carmen
Economics
  Adams      Terry
  Adaobi     Izuchukwu
  Berggren   Jeanette
  Garcia     Cesar
  Ifeoma     Nwanneka
  Jamuike    Ifeanacho
  Larsson    Naima
  Svensson   Noel
  Ugomma     Ifunanya
Engineering
  Axelsson   Erik
  Berg       Veronika
  Engström   Nancy
  Hicks      Cassie
  Keever     Bruce
  Micallef   Nicholas
  Mortensen  Sven
  Nilsson    Erna
  Tucker     Michael
  Yermolayeva Anna
English
  Andersson  Sarah
  Feng       Hanying
  Ivanova    Arina
  Jakobsson  Jesper
  Jensen     Christiane
  Johansson  Mark
  Kolpakova  Nadezhda
  Omelchenko Svetlana
  Urquhart   Donald
Mathematics
  Frost      Gaby
  Garcia     Hugo
  Hedlund    Anna
  Kovaleva   Katerina
  Lindgren   Max
  Maslova    Evgeniya
  Olsson     Ruth
  Sammut     Maria
  Sazonova   Anastasiya
Physics
  Åkesson    Sami
  Edwards    Amy E.
  Falzon     John
  Garcia     Debra
  Hansson    Sanna
  Mattsson   Martina
  Richardson Don
  Zabokritski Eugene
*/

La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :

var orderedQuery = departments
    .GroupJoin(students, department => department.ID, student => student.DepartmentID,
    (department, studentGroup) => new
    {
        DepartmentName = department.Name,
        Students = studentGroup.OrderBy(student => student.LastName)
    })
    .OrderBy(department => department.DepartmentName);


foreach (var departmentList in orderedQuery)
{
    Console.WriteLine(departmentList.DepartmentName);
    foreach (var student in departmentList.Students)
    {
        Console.WriteLine($"  {student.LastName,-10} {student.FirstName,-10}");
    }
}

Bien que vous puissiez utiliser une orderby clause avec une ou plusieurs séquences sources avant la jointure, nous ne le recommandons généralement pas. Certains fournisseurs LINQ peuvent ne pas conserver cette commande après la jointure. Pour plus d’informations, consultez la clause de jointure .

Voir aussi