Introduction aux requêtes LINQ (C#)

Une requête est une expression qui récupère des données d’une source de données. Des sources de données différentes ont divers langages de requête natifs, par exemple, SQL pour les bases de données relationnelles et XQuery pour XML. Les développeurs doivent apprendre un nouveau langage de requête pour chaque type de source de données ou format de données qu’ils doivent prendre en charge. LINQ simplifie cette situation en proposant un modèle de langage C# cohérent pour des types de formats et de sources de données. Dans une requête LINQ, vous travaillez toujours avec des objets C#. Vous utilisez les mêmes modèles d’encodage de base pour interroger et transformer des données en documents XML, bases de données SQL, collections .NET et tout autre format quand un fournisseur LINQ est disponible.

Les trois parties d’une opération de requête

Toutes les opérations de requête LINQ se composent de trois actions distinctes :

  1. Obtention de la source de données
  2. Création de la requête
  3. exécutez la requête.

L’exemple suivant montre comment les trois parties d’une opération de requête sont exprimées dans le code source. Cet exemple utilise un tableau d’entiers comme source de données pour des raisons pratiques. Toutefois, ces mêmes concepts s’appliquent également aux autres sources de données. Le reste de l’article fait référence à cet exemple.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

L’illustration suivante montre l’intégralité de l’opération de requête. Dans LINQ, l’exécution de la requête est distincte de la requête elle-même. En d’autres termes, vous ne récupérez aucune donnée en créant une variable de requête.

Diagram of the complete LINQ query operation.

Source de données

La source de données de l’exemple précédent est un tableau, elle prend en charge l’interface générique IEnumerable<T>. Cela signifie qu’elle peut être interrogée avec LINQ. Une requête est exécutée dans une instruction foreach, et foreach nécessite IEnumerable ou IEnumerable<T>. Les types qui prennent en charge IEnumerable<T> ou une interface dérivée, comme l’interface générique IQueryable<T>, sont appelés des types requêtables.

Un type requêtable ne nécessite aucune modification ni traitement spécial pour servir de source de données LINQ. Si les données sources ne sont pas déjà en mémoire en tant que type requêtable, le fournisseur LINQ doit le représenter comme tel. Par exemple, LINQ to XML charge un document XML dans un type requêtable XElement :

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Avec EntityFramework, vous créez un mappage relationnel d’objets entre les classes C# et votre schéma de base de données. Vous écrivez vos requêtes sur les objets, et à l’exécution, EntityFramework gère la communication avec la base de données. Dans l’exemple suivant, Customers représente une table spécifique de la base de données et le type du résultat de la requête, IQueryable<T>, dérive de IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Pour plus d’informations sur la création de types spécifiques de sources de données, consultez la documentation relative aux différents fournisseurs LINQ. Toutefois, la règle de base est très simple : une source de données LINQ est un objet qui prend en charge l’interface générique IEnumerable<T> ou une interface qui hérite de celui-ci, généralement IQueryable<T>.

Remarque

Les types tels que ArrayList qui prennent en charge l’interface non générique IEnumerable peuvent également être utilisés comme source de données Linq. Pour plus d’informations, consultez Guide pratique pour interroger une liste de tableaux avec LINQ (C#).

La requête

La requête spécifie les informations à récupérer à partir de la ou des sources de données. Si vous le souhaitez, une requête peut également spécifier la manière dont ces informations doivent être triées, regroupées et mises en forme avant d’être retournées. Une requête est stockée dans une variable de requête et initialisée avec une expression de requête. Vous utilisez la syntaxe de requête C# pour écrire des requêtes.

La requête de l’exemple précédent retourne tous les nombres pairs du tableau d’entiers. L’expression de requête contient trois clauses : from, where et select. (Si vous connaissez le langage SQL, vous remarquez que l’ordre des clauses est inverse à celui du langage SQL.) La clause from spécifie la source de données, la clause where applique le filtre et la clause select spécifie le type des éléments retournés. Toutes les clauses de requête sont évoquées de manière détaillée dans cette section. Pour le moment, le point important est que dans LINQ, la variable de requête elle-même n’effectue aucune action et ne retourne aucune donnée. Elle stocke simplement les informations qui seront nécessaires pour produire des résultats lors de l’exécution ultérieure de la requête. Pour obtenir plus d’informations sur la construction des requêtes, consultez Vue d’ensemble des opérateurs de requête standard (C#).

Remarque

Les requêtes peuvent également être exprimées à l’aide d’une syntaxe de méthode. Pour plus d’informations, consultez Syntaxe de requête et syntaxe de méthode dans LINQ.

Exécution des requêtes

Exécution différée

La variable de requête elle-même stocke uniquement les commandes de requête. L’exécution de la requête est différée jusqu’à ce que vous itériez au sein de la variable de requête dans une instruction foreach. Ce concept est appelé exécution différée et est illustré dans l’exemple suivant :

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

C’est dans l’instruction foreach que les résultats de requête sont récupérés. Par exemple, dans la requête précédente, la variable d’itération num contient chaque valeur (une à la fois) de la séquence retournée.

Étant donné que la variable de requête ne contient jamais les résultats de requête, vous pouvez l’exécuter aussi souvent que vous le voulez pour récupérer les données mises à jour. Par exemple, il est possible que vous disposiez d’une base de données qui est constamment mise à jour par une application distincte. Dans votre application, vous pouvez créer une requête qui récupère les données les plus récentes, puis l’exécuter à intervalles réguliers pour obtenir les résultats mis à jour.

Forcer l’exécution immédiate

Les requêtes qui exécutent des fonctions d’agrégation sur une plage d’éléments sources doivent d’abord itérer au sein de ces éléments. Ces requêtes sont par exemple Count, Max, Average et First. Ces méthodes s’exécutent sans instruction foreach explicite, car la requête elle-même doit utiliser foreach pour retourner un résultat. Ces requêtes retournent une seule valeur, et non une collection IEnumerable. La requête suivante retourne un nombre de chiffres pairs du tableau source :

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Pour forcer l’exécution immédiate de n’importe quelle requête et mettre en cache ses résultats, vous pouvez appeler les méthodes ToList ou ToArray.

List<int> numQuery2 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToArray();

Vous pouvez également forcer l’exécution en plaçant la boucle foreach immédiatement après l’expression de requête. Toutefois, en appelant ToList ou ToArray, vous mettez également en cache toutes les données dans un même objet de collection.

Voir aussi