Vous êtes sur la page 1sur 14

LINQ

1. Introduction aux requêtes LINQ :


Une requête est une expression qui permet de récupérer des données à partir d’une source de
données. Il existe plusieurs langages de requêtage pour les différentes sources de données. Par
exemple SQL pour les bases de données relationnelles, XQuery pour le XML, etc. Les
développeurs se sont donc retrouvés dans l’obligation d’apprendre un nouveau langage de
requetage pour chaque source de données. Le langage LINQ simplifie cette opération en offrant
un modèle de requetage qui fait abstraction des sources de données en manipulant des objets.
Les requêtes LINQ suivent le même modèle pour interroger que ce soit pour interroger des
bases de données SQL, des documents XML, des collections .Net ou n’importe quel format pour
lequel le provider LINQ est disponible

1.1. Les étapes de l’opération de requêtage :


Toutes les opérations de requêtage LINQ se déroulent en trois étapes :
1- Récupérer la source de données
2- Créer la requête
3- Exécuter la requête
L’exemple suivant illustre ces trois étapes en appliquant une requête LINQ sur un tableau
(array) comme source de données. Il est à noter que le même concept s’applique pour toute
autre source de données.

classIntroToLINQ
{
staticvoid Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = newint[7] { 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);
}
}
}

La figure suivante illustre l’opération de requêtage. Dans le langage LINQ, l’exécution


de la requête est indépendante de la requête en soit. En d’autres termes, écrire une
requête ne déclenche pas la récupération des données, pour ce faire il faut exécuter
la requête.

1.2. Exécution des requêtes LINQ


1.2.1. Exécution différée :
Comme précédemment indiqué, une variable « query » stocke la requête mais ne
l’exécute pas. L’exécution s’effectue en différé lorsqu’on effectue une itération de la
dite variable dans une boucle foreach. L’exemple suivant illustre l’exécution d’une
requête :

// Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
1.2.2. Forcer l’exécution immédiate
Les requêtes qui exécutent des fonctions d'agrégation sur un intervalle d'éléments
récupérés à partir d’une source de données doivent d'abord parcourir ces éléments.
Ceci est le cas des requêtes Count, Max, Average et First. Ces requêtes s’exécutent
donc sans expliciter la boucle d’exécution foreach. Ce type de requêtes retourne une
seule valeur et non pas une collection IEnumerable. L’exemple suivant retourne un
« count » qui calcule le nombre de nombres paires dans le tableau
d’entiers « numbers »:

int[] numbers = newint[7] { 0, 1, 2, 3, 4, 5, 6 };


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

int evenNumCount = evenNumQuery.Count();

Afin de force l’exécution immédiate de n’importe quelle requête et stocker son


résultat dans le cash, nous pouvons faire appel aux méthodes ToList<TSource> ou
ToArray<TSource>.

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();

Nous pouvons également forcer l’exécution de la requête en plaçant la boucle foreach


directement après la déclaration de la requête. L’appel de ToList ou de ToArray va,
cependant, stocker toutes les données en cash dans une seule collection d’objets.

2. Les requêtes LINQ de base


2.1. Filtrage
Les requêtage de filtrage sont les plus utilisées. Il s’agit de filtrer une liste de données selon une
expression booléenne. Le filtre impose à la requête de ne retourner que les éléments qui
répondent à l’expression booléenne par true. L’exemple suivant illustre un filtre qui exclue tous
les customers dont la « city » n’est pas london. Autrement dit ceux pour qui
(cust.city== « london » ) = false :

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

On peut également utiliser les opérateurs logiques AND et OR pour appliquer autant de filtres
que l’on souhaite dans la clause where. Par exemple, afin de retourner une seul customer dont
la city est london et dont le nom est « Devon » on peut écrire le code suivant :

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

Pour retourner des customer de paris ou de London, on peut écrire la requête suivante :

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

2.2. Ordonnancement
Il est souvent utilise d’ordonner une list de résultat d’une requête. La clause orderby permet
d’ordonner la séquence retournée selon le comparateur par défaut du type à ordonner. Par
exemple, la requête suivante retourne une liste de customers ordonnés par nom. Parce que
name est de type string, le comparateur par défaut effectue un ordonnancement alphabétique
de A à Z.

var queryLondonCustomers3 =
from cust in customers
where cust.City == "London"
orderby cust.Name ascending
select cust;
Si l’on veut ordonner les éléments dans de z à a, utiliser la clause descending.

2.3. Groupement
La clause group permet de grouper les résultats selon une clé (key) qu’on spécifie. Par exemple,
on peut spécifier que les résultats doivent être groupés par city de façon à ce que tous les
customers de london ou de paris soient dans des groupes distincts. Dans ce cas, cust.city est
notre clé.
Quand on termine une requête avec la clause group, le résultat de la requête sera de la forme
d’une liste de listes. Chaque élément de la liste est un objet qui a une clé et une liste d’élément
groupés selon cette clé. Lorsqu’on parcourt le résultat de la requête (foreach), on doit utiliser
une deuxième boucle de parcours imbriquée. La boucle externe parcourt la liste de groupes, et
la boucle interne parcours les éléments d’un groupe.
Si l’on doit faire référence au résultat d’une opération de groupement, on peut utiliser le mot
clé into pour créer un identifiant qu’on peut utiliser dans d’ultérieures requêtes. La requête
suivante retourne les groupes qui contiennent plus que deux customers.

2.4. Jointure
L’opération de jointure associe deux séquences d’éléments qui ne sont pas explicitement liés
dans la source de données. On peut, par exemple, effectuer une jointure pour récupérer les
customers et les distributors qui ont la même location. Dans le langage LINQ, la clause join
s’applique aux collections d’objets et non directement aux tables d’une base de données.

Dans le langage LINQ on n’utilise pas la clause join aussi souvent que dans le SQL
Car les clés étrangères dans le langage LINQ sont représentées dans l’objet interrogé en tant
que propriétés. Par exemple l’objet customer contient une collection d’objets de type Order.
Dans ce cas au lieu d’effectuer une jointure, on peut accéder directement à la liste d’ordre
moyennant l’expression suivante :

3. Transformation des données avec LINQ


Le LINQ (Langage Integrated Query) n’est pas uniquement un langage de récupération de données. C’est
un outil très puissant qui permet aussi de transformer les données. En utilisant une requête LINQ, on
peut utiliser une source de données et la modifier de maintes façons pour en créer un output différent.
On peut modifier la séquences résultat d’une requête en l’ordonnant, la groupant ect. Mais on peut
surtout créer de nouveaux types. Ceci est indiqué dans la clause select.

3.1. Joindre de multiples entrés dans une seule séquence de sortie


On peut utiliser une requête LINQ pour créer une séquence, résultat de la requête, contenant
des éléments de différentes séquences interrogées en entrée. L’exemple suivant illustre la
combinaison de deux séquences d’éléments :
3.2. Sélectionner un sous-ensemble de chaque élément source
Il existe deux façons de sélectionner un sous-ensemble de chaque élément d’une séquence
source récupérée.
1- Sélectionner uniquement un membre de l’élément source en utilisant l’opération point
.
( ). l’exemple suivant illustre ce cas :
var query =
from cust in Customers
select cust.City;

2- Créer des éléments qui continent une ou plusieurs propriétés de l’élément source. On
peut utiliser un initialiseur d’objet avec soit un objet nommé ou un type anonyme.
L’exemple suivant illustre l’utilisation d’un type anonyme qui encapsule deux propriétés
de chaque élément « customer » :
var query =
from cust in Customer
select new {Name = cust.Name, City = cust.City};

3.3. Transformer des objets en mémoire en XML


Les requêtes LINQ permettent de transformer les données entre des objets en mémoire, des
bases de données SQL, ADO .NET Datasets et documents ou streams XML. L’exemple suivant
transforme des objets en mémoire en éléments XML.
Le code génère l’output XML suivant :
4. Les types de relations dans les requêtes LINQ
Afin d’écrire efficacement des requêtes, on doit comprendre les types de variables dans une
requête complète. Une fois que vous aurez compris les relations entre les types de variables,
vous pourrez facilement comprendre ce qui ce passe en arrière-plan lors de l’utilisation d’une
variable déclarée implicitement avec le mot clés var.
Les requêtes LINQ sont fortement typées aux éléments sources à l’intérieur de la requête elle-
même et lors de l’exécution de la requête. Le type de variable à l’intérieur de la requête doit
donc être compatible au type de l’élément source et au type de la variable d’itération dans la
boucle d’exécution foreach. Ce typage fort garantie que les erreurs de typages soient détectées
lors de la compilation lorsqu’on peut les corriger avant que l’utilisateur n’y soit confronté.
Afin de démontrer ces relations de type, la plupart des exemples qui suivent utilisent des types
explicites pour toutes les variables. Le dernier exemple montre comment les mêmes principes
sont applicables même lorsque vous utilisez le typage implicite en utilisant var.
4.1. Requêtes qui ne transforment pas la source de données
La figure suivante illustre une requête LINQ sur des Objets qui n’effectue aucune transformation
des données. La source contient une séquence de strings et le résultat de la requête est
également une séquence de strings.

1) Le type de la source de données détermine le type de la variable à interroger (clause


from).
2) Le type de l’objet sélectionné détermine le type de la variable de requête. Dans ce
ca, name est de type string d’où la variable de requête est un IEnumerable<string>.
3) La variable de requête est parcourue par une boucle foreach. La variable d’itération
est de type string parce que la variable de requête est une séquence de strings.
4.2. Requêtes qui transforment la source de données
La figure suivante illustre une requête LINQ qui effectue une simple transformation de
données. La requête récupère une simple séquence de Customers et en sélectionne
uniquement la propriété name. Parce que name est de type string, la requête retourne une
séquence de strings.

1) Le type de la source de données détermine le type de la variable à interroger (clause


from).
2) La clause select retourne la propriété name au lieu de retourner un objet de type
Customer. Le type de la séquence IQueryable retournée, custNameQuery, est donc
string et non pas Customer.
3) Parce que custNameQuery est une sequence de strings, la variable d’itération de la
boucle d’exécution foreach doit être de type string.
0
1) Le type de la source de données est toujours le type de la variable à interroger
(clause from) dans la raquête.
2) La clause select retourne un type anonyme, d’où la variable de requête doit être de
type anonyme et ceux en employant le mot clé var.
3) Etant donné que le type de la variable de requête est implicite, la variable d’itération
dans la boucle foreach doit également être implicite.

5. Syntaxe des requêtes et syntaxe des méthodes dans le


langage LINQ
La plupart des requêtes précédemment citées sont écrites en utilisant la syntaxe déclarative des
requêtes LINQ. Cette syntaxe doit cependant être traduite en une méthode compréhensible par
la CLR au moment de la compilation. Cette méthode invoque les opérateurs standards tels que
Where, Select, GroupBy, Join, Max et Average. On peut cependant les invoquer directement en
utilisant la syntaxe de méthode à la place de la syntaxe de requête.
La syntaxe des requêtes et la syntaxe de méthode sont sémantiquement identiques mais
plusieurs développeurs trouvent que la syntaxe des requêtes est plus simple et facile à lire.
Certaines requêtes doivent être exprimées en appel de méthode. Par exemple, on doit utiliser
un appel de méthode pour écrire une requête qui récupère le nombre d’éléments qui
correspondent à une condition spécifique. On doit également utiliser l’appel de méthode dans
une requête qui récupère les éléments qui ont la valeur maximale dans une séquence source. La
documentation de référence pour les opérateurs de requêtes standards dans le namespace
System.Linq utilise la syntaxe de méthode. C’est dans ce sens qu’il est préférable d’être familier
avec l’utilisation des méthodes dans les requêtes.
5.1. Méthodes d’extension des opérateurs de requêtage standards
L’exemple suivant illustre une simple requête et son équivalent écrit en tant que requête basée
sur une méthode.
classQueryVMethodSyntax
{
staticvoid Main()
{
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 + " ");
}
// Keep the console open in debug mode.
Console.WriteLine(System.Environment.NewLine);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/*
Output:
6 8 10 12
6 8 10 12
*/

Le résultat des deux exemples est identique ainsi que le type de la variable des deux
IEnumerable<T>.
Pour mieux comprendre les requêtes basées sur les méthodes, on va les examiner de plus près.
La clause Where est exprimée en tant que méthode d’une instance de l’objet numbers.
numbers, rappelons-le, est de type IEnumerable<int>. Le type générique IEnumerable n’a pas
de methode Where, cependant, lorsqu’on invoque la liste auto-complete intelliSense de Visual
Studio, non seulement la méthode Where y figure, mais aussi d’autres méthodes tel que Select,
SelectMany, Join et OrderBy. Ce sont tous des opérateurs de requêtage standards.

Même si en apparence IEnumerable<T> a été redéfinie pour inclure toutes ces méthodes, il n’en
est rien. Il s’agit tout simplement de méthodes d’extension.

5.2. Expressions lambda


Dans le précédent exemple l’expression de condition est passé à la clause where en tant
qu’expression lambda (num => (num%2 == 0)). Ceci est le moyen le plus convenable d’écrire un
code à l’intérieur d’une requête, la requête risque sinon d’être trop compliquée à lire soit en
utilisant un delegate ou autre. En C# le => signifie (goes to). Les variables à gauche de
l’expression sont les paramètres et l’expression à droite le traitement.

5.3. Composition de requêtes


Dans le dernier exemple, la méthode OrderBy est invoqué via l’utilisation de l’opérateur point (.)
dans la clause Where. Where génère une séquence filtrée et orderBy procède ensuite à
l’ordonnancement de cette séquence. C’est ce que le compilateur fait en coulisses lorsqu’on
écrit une requête avec la syntaxe de requête. Et parce qu'une variable de requête ne stocke pas
les résultats de la requête, vous pouvez la modifier ou l'utiliser comme base pour une nouvelle
requête à tout moment, même après qu'elle ait été exécuté.

Vous aimerez peut-être aussi