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 reeks instructies die beschrijven 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 worden geproduceerd.

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 'reeks' XML-elementen (hoewel XML-elementen hiërarchisch zijn ingedeeld in een structuurstructuur). 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 altijd de brongegevens als een IEnumerable<T> of IQueryable<T> verzameling. In LINQ naar XML worden de brongegevens bijvoorbeeld zichtbaar gemaakt als een IEnumerableXElement<>.

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 dit 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 matrix met gehele getallen:

      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 voordat de aanroep naar de Enumerable.Count methode wordt uitgevoerd. 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 moeten worden herhaald om het aantal elementen te bepalen dat wordt geretourneerd door highScoresQuery.

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 from-component en moet eindigen met een select - of groepscomponent . Tussen de eerste component en de laatste fromselect of group component kan deze een of meer van deze optionele componenten bevatten: waarbij, orderby, join, let en zelfs een andere component van componenten. U kunt het in trefwoord ook gebruiken om het resultaat van een join of group component in te schakelen als de bron voor meer queryclausules 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 overschreven of een directe aanroep van IEnumerator.MoveNext() de 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 scoreQuery is een queryvariabele, die soms alleen een query wordt 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 deze 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 beide queryMajorCities queryvariabelen queryMajorCities2 :

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 > 100000
    select city;

// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
    Console.WriteLine(city);
}

// Output:
// City { Population = 120000 }
// City { Population = 112000 }
// City { Population = 150340 }

// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

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 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> impliciete of expliciet getypte.

Een query-expressie starten

Een query-expressie moet beginnen met een from component. 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. In het volgende voorbeeld, omdat countries dit 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 > 500000 //sq km
    select country;

De bereikvariabele valt 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 Country objecten hebt die elk een verzameling City objecten bevat met de naam Cities. Als u een query wilt uitvoeren op de City objecten in elk Countryobject, 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;

Zie de component from voor meer informatie.

Een query-expressie beëindigen

Een query-expressie moet eindigen met een group component of een select component.

group component

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 de groepscomponent voor meer informatie over groeperen.

component selecteren

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 Country de gegevensbron objecten. De orderby component sorteert 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 projecteert de select component 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 vereiste omdat de query een anoniem type produceert.

Zie de select-component voor meer informatie over alle manieren waarop een select component kan worden gebruikt om brongegevens te transformeren.

Vervolgen met in

U kunt het into trefwoord in een select of group component gebruiken om een tijdelijke id te maken waarin een query wordt opgeslagen. Gebruik de into component wanneer u extra querybewerkingen op een query moet uitvoeren na een groeperings- of selectiebewerking. In het volgende voorbeeld countries worden ze gegroepeerd op basis van de populatie in bereiken van 10 miljoen. Nadat deze groepen zijn gemaakt, filteren meer componenten sommige groepen en sorteert u de groepen in oplopende volgorde. Als u deze extra bewerkingen wilt uitvoeren, is de voortzetting countryGroup vereist.

// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int)country.Population / 10_000_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 voor meer informatie.

Filteren, ordenen en samenvoegen

Tussen de beginclausule from en het einde select of group de component zijn alle andere componenten (where, join, orderby, from) letoptioneel. Een van de optionele componenten kan nul of meerdere keren worden gebruikt in een querytekst.

where-component

Gebruik de where component 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 < 200000 and > 100000
    select city;

Zie de where-component voor meer informatie.

orderby-component

Gebruik de orderby component 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 Area eigenschap. Vervolgens wordt een secundaire sortering uitgevoerd met behulp van de Population eigenschap.

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending
    select country;

Het trefwoord is optioneel. Dit ascending is de standaardsortering als er geen volgorde is opgegeven. Zie de orderby-component voor meer informatie.

join-component

Gebruik de join component om elementen van 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 moet worden opgeslagen in de uitvoerreeks. 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 objecten gekoppeld prod waarvan Category de eigenschap overeenkomt met een van de categorieën in de categories tekenreeksmatrix. Producten waarvan Category de tekenreeks categories niet overeenkomt, worden uitgefilterd. De select instructie projecteert een nieuw type waarvan de eigenschappen afkomstig zijn van 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 in . Zie join-component voor meer informatie.

let-component

Gebruik de let component 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

Zie let-component voor meer informatie.

Subquery's in een query-expressie

Een querycomponent kan zelf een query-expressie bevatten, die soms een subquery wordt 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 voor een groeperingsbewerking voor meer informatie.

Zie ook