Delen via


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 een int[]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 een string. Let op het nieuwe type highScoresQuery.

    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 highScoresQuerywordt 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 Citiesbevat. 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 selectvoor 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 intovoor 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.

Zie ook