Notions de base de l’expression de requête
Cet article présente les concepts de base liés aux expressions de requête en C#.
Qu’est-ce qu’une requête et ce qu’elle fait ?
Une requête est un ensemble d’instructions qui décrivent les données à récupérer à partir d’une source de données donnée (ou sources) et la forme et l’organisation dont les données retournées doivent avoir. Une requête est distincte des résultats qu’elle produit.
En règle générale, les données sources sont organisées logiquement sous la forme d’une séquence d’éléments du même type. Par exemple, une table de base de données SQL contient une séquence de lignes. Dans un fichier XML, il existe une « séquence » d’éléments XML (bien que les éléments XML soient organisés hiérarchiquement dans une arborescence). Une collection en mémoire contient une séquence d’objets.
Du point de vue d’une application, le type et la structure spécifiques des données sources d’origine ne sont pas importants. L’application voit toujours les données sources sous la forme d’une collection IEnumerable<T> ou IQueryable<T>. Par exemple, dans LINQ to XML, les données sources sont rendues visibles en tant que IEnumerable
<XElement>.
Étant donné cette séquence source, une requête peut effectuer l’une des trois opérations suivantes :
Récupérez un sous-ensemble des éléments pour produire une nouvelle séquence sans modifier les éléments individuels. La requête peut ensuite trier ou regrouper la séquence retournée de différentes façons, comme illustré dans l’exemple suivant (supposons que
scores
est unint[]
) :IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
Récupérez une séquence d’éléments comme dans l’exemple précédent, mais transformez-les en un nouveau type d’objet. Par exemple, une requête peut récupérer uniquement les noms de famille de certains enregistrements clients dans une source de données. Il peut également récupérer l’enregistrement complet, puis l’utiliser pour construire un autre type d’objet en mémoire ou même des données XML avant de générer la séquence de résultats finale. L’exemple suivant montre une projection d’un
int
à unstring
. Notez le nouveau type dehighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
Récupérez une valeur singleton sur les données sources, telles que :
Nombre d’éléments qui correspondent à une condition donnée.
Élément dont la valeur est la plus élevée ou la moins élevée.
Premier élément qui correspond à une condition ou à la somme de valeurs particulières dans un ensemble d’éléments spécifié. Par exemple, la requête suivante retourne le nombre de scores supérieurs à 80 à partir du tableau entier
scores
:var highScoreCount = ( from score in scores where score > 80 select score ).Count();
Dans l’exemple précédent, notez l’utilisation de parenthèses autour de l’expression de requête avant l’appel à la méthode Enumerable.Count. Vous pouvez également utiliser une nouvelle variable pour stocker le résultat concret.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
Dans l’exemple précédent, la requête est exécutée dans l’appel à Count
, car Count
doit itérer sur les résultats afin de déterminer le nombre d’éléments retournés par highScoresQuery
.
Qu’est-ce qu’une expression de requête ?
Une expression de requête est une requête exprimée en syntaxe de requête. Une expression de requête est une construction de langage de première classe. Elle est tout comme n’importe quelle autre expression et peut être utilisée dans n’importe quel contexte dans lequel une expression C# est valide. Une expression de requête se compose d’un ensemble de clauses écrites dans une syntaxe déclarative similaire à SQL ou XQuery. Chaque clause contient à son tour une ou plusieurs expressions C#, et ces expressions peuvent elles-mêmes être une expression de requête ou contenir une expression de requête.
Une expression de requête doit commencer par une clause from et doit se terminer par une clause select ou group. Entre la première clause from
et la dernière clause select
ou group
, il peut contenir une ou plusieurs de ces clauses optionnelles : where, orderby, join, let et même une autre clause from. Vous pouvez également utiliser le mot-clé dans pour que le résultat d'une clause join
ou group
serve de source à d'autres clauses de requête dans la même expression de requête.
Variable de requête
Dans LINQ, une variable de requête correspond à n’importe quelle variable qui stocke une requête au lieu des résultats d’une requête. Plus précisément, une variable de requête est toujours un type énumérable qui produit une séquence d’éléments lorsqu’elle est itérée dans une instruction foreach
ou un appel direct à sa méthode de IEnumerator.MoveNext().
Remarque
Les exemples de cet article utilisent la source de données et les exemples de données suivants.
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
L’exemple de code suivant montre une expression de requête simple avec une source de données, une clause de filtrage, une clause de classement et aucune transformation des éléments sources. La clause select
met fin à la requête.
// Data source.
int[] scores = [90, 71, 82, 93, 75, 82];
// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group
// Execute the query to produce the results
foreach (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
// Output: 93 90 82 82
Dans l’exemple précédent, scoreQuery
est une variable de requête , parfois appelée simplement une requête . La variable de requête ne stocke aucune donnée de résultat réelle, produite dans la boucle foreach
. Et lorsque l’instruction foreach
s’exécute, les résultats de la requête ne sont pas retournés par le biais de la variable de requête scoreQuery
. En revanche, elles sont renvoyées par l’intermédiaire de la variable d’itération testScore
. La variable scoreQuery
peut être itérée dans une deuxième boucle foreach
. Elle produit les mêmes résultats tant qu’aucune source de données n’a été modifiée.
Une variable de requête peut stocker une requête exprimée dans la syntaxe de requête ou la syntaxe de méthode, ou une combinaison des deux. Dans les exemples suivants, les queryMajorCities
et les queryMajorCities2
sont des variables de requête :
City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 30_000_000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 30_000_000);
// Execute the query to produce the results
foreach (City city in queryMajorCities2)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
En revanche, les deux exemples suivants montrent des variables qui ne sont pas des variables de requête même si chacune est initialisée avec une requête. Elles ne sont pas des variables de requête, car elles stockent les résultats :
var highestScore = (
from score in scores
select score
).Max();
// or split the expression
IEnumerable<int> scoreQuery =
from score in scores
select score;
var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var largeCitiesList = (
from country in countries
from city in country.Cities
where city.Population > 10000
select city
).ToList();
// or split the expression
IEnumerable<City> largeCitiesQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
var largeCitiesList2 = largeCitiesQuery.ToList();
Saisie explicite et implicite des variables de requête
Cette documentation fournit généralement le type explicite de la variable de requête afin d’afficher la relation de type entre la variable de requête et la clause select. Toutefois, vous pouvez également utiliser le mot clé var pour indiquer au compilateur de déduire le type d’une variable de requête (ou toute autre variable locale) au moment de la compilation. Par exemple, l’exemple de requête présenté précédemment dans cet article peut également être exprimé à l’aide de la saisie implicite :
var queryCities =
from city in cities
where city.Population > 100000
select city;
Dans l’exemple précédent, l’utilisation de var est facultative. queryCities
est un IEnumerable<City>
qu’il soit typé implicitement ou explicitement.
Démarrage d’une expression de requête
Une expression de requête doit commencer par une clause from
. Il spécifie une source de données avec une variable de plage. La variable de plage représente chaque élément successif de la séquence source, car la séquence source est en cours de traversée. La variable de plage est fortement typée en fonction du type d’éléments dans la source de données. Dans l’exemple suivant, étant donné que countries
est un tableau d’objets Country
, la variable de plage est également typée comme Country
. Étant donné que la variable de portée est fortement typée, vous pouvez utiliser l’opérateur point pour accéder à tous les membres disponibles du type.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
La variable de portée est dans la portée tant que la requête n’est pas quittée avec un point-virgule ou une clause continuation.
Une expression de requête peut contenir plusieurs clauses from
. Utilisez d’autres clauses from
lorsque chaque élément de la séquence source est lui-même une collection ou contient une collection. Par exemple, supposons que vous disposez d’une collection d’objets Country
, chacun contenant une collection d’objets City
nommés Cities
. Pour interroger les objets City
dans chaque Country
, utilisez deux clauses from
comme indiqué ici :
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
Pour plus d’informations, consultez de la clause.
Fin d’une expression de requête
Une expression de requête doit se terminer par une clause group
ou une clause select
.
Le groupe de clauses
Utilisez la clause group
pour produire une séquence de groupes organisés par une clé que vous spécifiez. La clé peut être n’importe quel type de données. Par exemple, la requête suivante crée une séquence de groupes qui contient un ou plusieurs objets Country
et dont la clé est un type char
avec la valeur étant la première lettre des noms des pays.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Pour plus d’informations sur le regroupement, consultez clause de groupe.
select, clause
Utilisez la clause select
pour produire tous les autres types de séquences. Une clause select
simple produit simplement une séquence du même type d’objets que les objets contenus dans la source de données. Dans cet exemple, la source de données contient des objets Country
. La clause orderby
trie simplement les éléments dans un nouvel ordre et la clause select
produit une séquence des objets Country
réorganisé.
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
La clause select
peut être utilisée pour transformer les données sources en séquences de nouveaux types. Cette transformation est également appelée projection. Dans l’exemple suivant, la clause select
projets une séquence de types anonymes qui contient uniquement un sous-ensemble des champs de l’élément d’origine. Les nouveaux objets sont initialisés à l’aide d’un initialiseur d’objet.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
Ainsi, dans cet exemple, la var
est requise, car la requête produit un type anonyme.
Pour plus d'informations sur toutes les façons dont une clause
Continuations avec into
Vous pouvez utiliser le mot clé into
dans une clause select
ou group
pour créer un identificateur temporaire qui stocke une requête. Utilisez la clause into
lorsque vous devez effectuer des opérations de requête supplémentaires sur une requête après un regroupement ou une opération de sélection. Dans l’exemple suivant, les countries
sont regroupés par tranches de population de 10 millions. Une fois ces groupes créés, d’autres clauses filtrent certains groupes, puis trient les groupes dans l’ordre croissant. Pour effectuer ces opérations supplémentaires, la continuation représentée par countryGroup
est requise.
// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
from country in countries
let percentile = (int)country.Population / 1_000
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;
// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
Console.WriteLine(grouping.Key);
foreach (var country in grouping)
{
Console.WriteLine(country.Name + ":" + country.Population);
}
}
Pour plus d’informations, consultez into.
Filtrage, classement et jointure
Entre la clause from
de début et la clause select
de fin ou group
, toutes les autres clauses (where
, join
, orderby
, from
, let
) sont facultatives. Toutes les clauses facultatives peuvent être utilisées zéro fois ou plusieurs fois dans un corps de requête.
La clause where
Utilisez la clause where
pour filtrer les éléments des données sources en fonction d’une ou plusieurs expressions de prédicat. La clause where
de l’exemple suivant comporte un prédicat avec deux conditions.
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
Pour plus d’informations, consultez where, clause.
La clause orderby
Utilisez la clause orderby
pour trier les résultats dans l’ordre croissant ou décroissant. Vous pouvez également spécifier des commandes de tri secondaires. L’exemple suivant effectue un tri principal sur les objets country
à l’aide de la propriété Area
. Il effectue ensuite un tri secondaire à l’aide de la propriété Population
.
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
Le mot clé ascending
est facultatif ; il s’agit de l’ordre de tri par défaut si aucun ordre n’est spécifié. Pour plus d’informations, consultez orderby, clause.
Clause de jointure
Utilisez la clause join
pour associer et/ou combiner des éléments d’une source de données à des éléments d’une autre source de données en fonction d’une comparaison d’égalité entre les clés spécifiées dans chaque élément. Dans LINQ, les opérations de jointure sont effectuées sur des séquences d’objets dont les éléments sont des types différents. Après avoir joint deux séquences, vous devez utiliser une instruction select
ou group
pour spécifier l’élément à stocker dans la séquence de sortie. Vous pouvez également utiliser un type anonyme pour combiner des propriétés de chaque ensemble d’éléments associés dans un nouveau type pour la séquence de sortie. L’exemple suivant associe des objets prod
dont la propriété Category
correspond à l’une des catégories du tableau categories
de chaînes. Les produits dont Category
ne correspondent à aucune chaîne dans categories
sont filtrés. L’instruction select
projette un nouveau type dont les propriétés sont extraites à la fois cat
et prod
.
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
Vous pouvez également effectuer une jointure de groupe en stockant les résultats de l’opération de join
dans une variable temporaire à l'aide du mot clé into. Pour plus d’informations, consultez la clause de jointure .
La clause let
Utilisez la clause let
pour stocker le résultat d’une expression, telle qu’un appel de méthode, dans une nouvelle variable de plage. Dans l’exemple suivant, la variable de plage firstName
stocke le premier élément du tableau de chaînes retourné par Split
.
string[] names = ["Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia"];
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;
foreach (var s in queryFirstNames)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
Pour plus d’informations, consultez let, clause.
Sous-requêtes dans une expression de requête
Une clause de requête peut contenir elle-même une expression de requête, parfois appelée sous-requête . Chaque sous-requête commence par sa propre clause from
qui ne pointe pas nécessairement vers la même source de données dans la première clause from
. Par exemple, la requête suivante montre une expression de requête utilisée dans l’instruction select pour récupérer les résultats d’une opération de regroupement.
var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};
Pour plus d’informations, consultez le Guide pratique pour effectuer une sous-requête sur une opération de regroupement.
Voir aussi
- mots clés de requête (LINQ)
- vue d’ensemble des opérateurs de requête standard