Basisbeginselen van query-expressies
In dit artikel worden de basisconcepten geïntroduceerd die betrekking hebben op query-expressies in C#.
Wat is een query en wat doet deze?
Een query is een set instructies die beschrijft welke gegevens moeten worden opgehaald uit een bepaalde gegevensbron (of bronnen) en welke vorm en organisatie de geretourneerde gegevens moeten hebben. Een query verschilt van de resultaten die het voortbrengt.
Over het algemeen worden de brongegevens logisch ingedeeld als een reeks elementen van hetzelfde type. Een SQL-databasetabel bevat bijvoorbeeld een reeks rijen. In een XML-bestand is er een "volgorde" van XML-elementen (hoewel XML-elementen hiërarchisch zijn ingedeeld in een boomstructuur). Een in-memory verzameling bevat een reeks objecten.
Vanuit het oogpunt van een toepassing is het specifieke type en de structuur van de oorspronkelijke brongegevens niet belangrijk. De toepassing ziet de brongegevens altijd als een IEnumerable<T> of IQueryable<T> verzameling. In LINQ naar XML worden de brongegevens bijvoorbeeld zichtbaar gemaakt als een IEnumerable
<XElement>.
Op basis van deze bronvolgorde kan een query een van de volgende drie dingen doen:
Haal een subset van de elementen op om een nieuwe reeks te produceren zonder de afzonderlijke elementen te wijzigen. De query kan vervolgens de geretourneerde volgorde op verschillende manieren sorteren of groeperen, zoals wordt weergegeven in het volgende voorbeeld (stel dat
scores
eenint[]
is):IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
Haal een reeks elementen op zoals in het vorige voorbeeld, maar transformeer ze naar een nieuw type object. Een query kan bijvoorbeeld alleen de familienamen ophalen uit bepaalde klantrecords in een gegevensbron. Of het kan de volledige record ophalen en vervolgens gebruiken om een ander objecttype in het geheugen of zelfs XML-gegevens te maken voordat de uiteindelijke resultatenreeks wordt gegenereerd. In het volgende voorbeeld ziet u een projectie van een
int
naar eenstring
. Let op het nieuwe typehighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
Haal een singleton-waarde op over de brongegevens, zoals:
Het aantal elementen dat overeenkomt met een bepaalde voorwaarde.
Het element met de grootste of laagste waarde.
Het eerste element dat overeenkomt met een voorwaarde of de som van bepaalde waarden in een opgegeven set elementen. Met de volgende query wordt bijvoorbeeld het aantal scores geretourneerd dat groter is dan 80 uit de
scores
integere array.var highScoreCount = ( from score in scores where score > 80 select score ).Count();
Let in het vorige voorbeeld op het gebruik van haakjes rond de query-expressie vóór de aanroep van de Enumerable.Count methode. U kunt ook een nieuwe variabele gebruiken om het concrete resultaat op te slaan.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
In het vorige voorbeeld wordt de query uitgevoerd in de aanroep naar Count
, omdat Count
de resultaten moet herhalen om het aantal elementen te bepalen dat door highScoresQuery
wordt geretourneerd.
Wat is een query-expressie?
Een query-expressie is een query die wordt uitgedrukt in de querysyntaxis. Een query-expressie is een eersteklas taalconstructie. Het is net als elke andere expressie en kan worden gebruikt in elke context waarin een C#-expressie geldig is. Een query-expressie bestaat uit een set componenten die zijn geschreven in een declaratieve syntaxis die vergelijkbaar is met SQL of XQuery. Elke component bevat een of meer C#-expressies en deze expressies kunnen zelf een query-expressie zijn of een query-expressie bevatten.
Een query-expressie moet beginnen met een uit clausule en moet eindigen met een select of groep clausule. Tussen de eerste from
-clausule en de laatste select
- of group
-clausule kan het een of meer van deze optionele clausules bevatten: waarin, orderby, join, let en zelfs nog een van clausules. U kunt het trefwoord into
gebruiken om het resultaat van een join
- of group
-clausule ook te laten dienen als bron voor meer query-clausules in dezelfde query-expressie.
Queryvariabele
In LINQ is een queryvariabele een variabele waarmee een query wordt opgeslagen in plaats van de resultaten van een query. In het bijzonder is een queryvariabele altijd een opsommingstype dat een reeks elementen produceert wanneer deze in een foreach
-instructie wordt doorlopen of bij een directe aanroep van zijn IEnumerator.MoveNext()-methode.
Notitie
In voorbeelden in dit artikel worden de volgende gegevensbron en voorbeeldgegevens gebruikt.
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)])
];
In het volgende codevoorbeeld ziet u een eenvoudige query-expressie met één gegevensbron, één filtercomponent, één bestelcomponent en geen transformatie van de bronelementen. De select
-component beëindigt de query.
// 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
In het vorige voorbeeld is scoreQuery
een queryvariabele, die soms alleen een querywordt genoemd. De queryvariabele slaat geen werkelijke resultaatgegevens op, die in de foreach
lus worden geproduceerd. En wanneer de foreach
-instructie wordt uitgevoerd, worden de queryresultaten niet geretourneerd via de queryvariabele scoreQuery
. In plaats daarvan worden ze geretourneerd via de iteratievariabele testScore
. De scoreQuery
variabele kan in een tweede foreach
lus worden herhaald. Het produceert dezelfde resultaten zolang het noch zelf, noch de gegevensbron is gewijzigd.
Een queryvariabele kan een query opslaan die wordt uitgedrukt in querysyntaxis of methodesyntaxis, of een combinatie van de twee. In de volgende voorbeelden zijn zowel queryMajorCities
als queryMajorCities2
queryvariabelen:
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 }
Aan de andere kant tonen de volgende twee voorbeelden variabelen die geen queryvariabelen zijn, ook al worden ze geïnitialiseerd met een query. Ze zijn geen queryvariabelen omdat ze resultaten opslaan:
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();
Expliciet en impliciet typen van queryvariabelen
Deze documentatie bevat meestal het expliciete type van de queryvariabele om de typerelatie tussen de queryvariabele en de select-component weer te geven. U kunt echter ook het trefwoord var gebruiken om de compiler te instrueren het type van een queryvariabele (of een andere lokale variabele) tijdens het compileren af te stellen. Het queryvoorbeeld dat eerder in dit artikel is weergegeven, kan bijvoorbeeld ook worden uitgedrukt met impliciet typen:
var queryCities =
from city in cities
where city.Population > 100000
select city;
In het voorgaande voorbeeld is het gebruik van var optioneel.
queryCities
is een IEnumerable<City>
, zowel impliciet als expliciet getypt.
Een query-expressie starten
Een query-expressie moet beginnen met een from
-clausule. Hiermee geeft u een gegevensbron samen met een bereikvariabele op. De bereikvariabele vertegenwoordigt elk opeenvolgend element in de bronreeks wanneer de bronreeks wordt doorkruist. De bereikvariabele wordt sterk getypt op basis van het type elementen in de gegevensbron. Omdat countries
in het volgende voorbeeld een matrix van Country
objecten is, wordt de bereikvariabele ook getypt als Country
. Omdat de bereikvariabele sterk is getypt, kunt u de puntoperator gebruiken om toegang te krijgen tot alle beschikbare leden van het type.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
De bereikvariabele bevindt zich binnen het bereik totdat de query wordt afgesloten met een puntkomma of met een vervolgcomponent.
Een query-expressie kan meerdere from
-componenten bevatten. Gebruik meer from
componenten wanneer elk element in de bronreeks zelf een verzameling is of een verzameling bevat. Stel dat u een verzameling van Country
objecten hebt, die elk een verzameling van City
objecten met de naam Cities
bevat. Als u een query wilt uitvoeren op de City
objecten in elke Country
, gebruikt u twee from
-componenten, zoals hier wordt weergegeven:
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
Voor meer informatie, zie clausule van clausule.
Een query-expressie beëindigen
Een query-expressie moet eindigen met een group
-component of een select
-component.
De groepsclausule
Gebruik de group
component om een reeks groepen te produceren die zijn ingedeeld op basis van een sleutel die u opgeeft. De sleutel kan elk gegevenstype zijn. Met de volgende query wordt bijvoorbeeld een reeks groepen gemaakt die een of meer Country
objecten bevat en waarvan de sleutel een char
type is met de waarde die de eerste letter van de namen van landen is.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Zie groepscomponentvoor meer informatie over groeperen.
select-verklaring
Gebruik de select
-component om alle andere typen reeksen te produceren. Een eenvoudige select
component produceert alleen een reeks van hetzelfde type objecten als de objecten die zich in de gegevensbron bevinden. In dit voorbeeld bevat de gegevensbron Country
objecten. De orderby
component sorteert alleen de elementen in een nieuwe volgorde en de select
component produceert een reeks van de opnieuw gerangschikte Country
objecten.
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
De select
-component kan worden gebruikt om brongegevens te transformeren in reeksen van nieuwe typen. Deze transformatie heet ook een projectie. In het volgende voorbeeld is de select
component projecten een reeks anonieme typen die slechts een subset van de velden in het oorspronkelijke element bevatten. De nieuwe objecten worden geïnitialiseerd met behulp van een object-initialisatiefunctie.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
In dit voorbeeld is de var
vereist omdat de query een anoniem type produceert.
Zie select
voor meer informatie over alle manieren waarop een -component kan worden gebruikt om brongegevens te transformeren.
Vervolgen met naar
U kunt het trefwoord into
in een select
of group
component gebruiken om een tijdelijke id te maken waarin een query wordt opgeslagen. Gebruik de into
-component wanneer u na een groepering of selectiebewerking extra querybewerkingen op een query moet uitvoeren. In het volgende voorbeeld worden countries
gegroepeerd op basis van de bevolking in reeksen van 10 miljoen. Nadat deze groepen zijn gemaakt, filteren meer clausules enkele groepen eruit en worden de groepen in oplopende volgorde gesorteerd. Als u deze extra bewerkingen wilt uitvoeren, is de voortzetting die wordt vertegenwoordigd door countryGroup
vereist.
// 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);
}
}
Zie into
voor meer informatie.
Filteren, ordenen en samenvoegen
Tussen de startclausule from
en de eindclausule select
of group
zijn alle andere clausules (where
, join
, orderby
, from
, let
) optioneel. Een van de optionele componenten kan nul of meerdere keren worden gebruikt in een querytekst.
De where-clausule
Gebruik de component where
om elementen uit de brongegevens te filteren op basis van een of meer predicaatexpressies. De where
component in het volgende voorbeeld heeft één predicaat met twee voorwaarden.
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
Voor meer informatie, zie where-clausule.
De ORDER BY-clausule
Gebruik de component orderby
om de resultaten in oplopende of aflopende volgorde te sorteren. U kunt ook secundaire sorteervolgordes opgeven. In het volgende voorbeeld wordt een primaire sortering uitgevoerd op de country
objecten met behulp van de eigenschap Area
. Vervolgens wordt een secundaire sortering uitgevoerd met behulp van de eigenschap Population
.
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
Het ascending
trefwoord is optioneel; dit is de standaardsorteerdvolgorde als er geen volgorde is opgegeven. Voor meer informatie, zie orderby-clausule.
De join-clause
Gebruik de component join
om elementen uit de ene gegevensbron te koppelen en/of te combineren met elementen uit een andere gegevensbron op basis van een gelijkheidsvergelijking tussen opgegeven sleutels in elk element. In LINQ worden joinbewerkingen uitgevoerd op reeksen objecten waarvan de elementen verschillende typen zijn. Nadat u twee reeksen hebt samengevoegd, moet u een select
- of group
instructie gebruiken om op te geven welk element in de uitvoerreeks moet worden opgeslagen. U kunt ook een anoniem type gebruiken om eigenschappen van elke set gekoppelde elementen te combineren in een nieuw type voor de uitvoervolgorde. In het volgende voorbeeld worden prod
objecten gekoppeld waarvan de eigenschap Category
overeenkomt met een van de categorieën in de categories
tekenreeksmatrix. Producten waarvan Category
niet overeenkomt met een tekenreeks in categories
worden uitgefilterd. De instructie select
projecteert een nieuw type waarvan de eigenschappen afkomstig zijn uit zowel cat
als prod
.
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
U kunt ook een groepsdeelname uitvoeren door de resultaten van de join
bewerking op te slaan in een tijdelijke variabele met behulp van het trefwoord into
. Voor meer informatie, zie join-clausule.
De let-clausule
Gebruik de component let
om het resultaat van een expressie, zoals een methodeaanroep, op te slaan in een nieuwe bereikvariabele. In het volgende voorbeeld slaat de bereikvariabele firstName
het eerste element op van de matrix met tekenreeksen die worden geretourneerd door 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
Voor meer informatie, zie let-clausule.
Subquery's in een query-expressie
Een queryclausule kan zelf een queryexpressie bevatten, die soms als een subquerywordt genoemd. Elke subquery begint met een eigen from
component die niet noodzakelijkerwijs verwijst naar dezelfde gegevensbron in de eerste from
component. De volgende query toont bijvoorbeeld een query-expressie die wordt gebruikt in de select-instructie om de resultaten van een groeperingsbewerking op te halen.
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()
};
Zie Een subquery uitvoeren op een groeperingsbewerkingvoor meer informatie.