Inleiding tot LINQ-query's in C#

Een query is een expressie waarmee gegevens uit een gegevensbron worden opgehaald. Verschillende gegevensbronnen hebben verschillende systeemeigen querytalen, bijvoorbeeld SQL voor relationele databases en XQuery voor XML. Ontwikkelaars moeten een nieuwe querytaal leren voor elk type gegevensbron of gegevensindeling die ze moeten ondersteunen. LINQ vereenvoudigt deze situatie door een consistent C#-taalmodel te bieden voor soorten gegevensbronnen en indelingen. In een LINQ-query werkt u altijd met C#-objecten. U gebruikt dezelfde basiscoderingspatronen om gegevens in XML-documenten, SQL-databases, .NET-verzamelingen en andere indelingen op te vragen en te transformeren wanneer een LINQ-provider beschikbaar is.

Drie delen van een querybewerking

Alle LINQ-querybewerkingen bestaan uit drie afzonderlijke acties:

  1. Haal de gegevensbron op.
  2. Maak de query.
  3. Voer de query uit.

In het volgende voorbeeld ziet u hoe de drie onderdelen van een querybewerking worden uitgedrukt in broncode. In het voorbeeld wordt voor het gemak een matrix met gehele getallen gebruikt als gegevensbron; Dezelfde concepten zijn echter ook van toepassing op andere gegevensbronnen. Dit voorbeeld wordt in de rest van dit artikel genoemd.

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

In de volgende afbeelding ziet u de volledige querybewerking. In LINQ is de uitvoering van de query anders dan de query zelf. Met andere woorden, u haalt geen gegevens op door een queryvariabele te maken.

Diagram van de volledige LINQ-querybewerking.

De gegevensbron

De gegevensbron in het voorgaande voorbeeld is een matrix die ondersteuning biedt voor de algemene IEnumerable<T> interface. Dit betekent dat er query's kunnen worden uitgevoerd met LINQ. Een query wordt uitgevoerd in een foreach instructie en foreach vereist IEnumerable of IEnumerable<T>. Typen die ondersteuning bieden IEnumerable<T> voor of een afgeleide interface, zoals de algemeneIQueryable<T>, worden querybare typen genoemd.

Een opvraagbaar type vereist geen wijziging of speciale behandeling om te fungeren als een LINQ-gegevensbron. Als de brongegevens zich nog niet als querybaar type in het geheugen bevinden, moet de LINQ-provider deze als zodanig vertegenwoordigen. LinQ naar XML laadt bijvoorbeeld een XML-document in een querybaar XElement type:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Met EntityFramework maakt u een object-relationele toewijzing tussen C#-klassen en uw databaseschema. U schrijft uw query's op de objecten en tijdens runtime verwerkt EntityFramework de communicatie met de database. In het volgende voorbeeld Customers vertegenwoordigt u een specifieke tabel in de database en het type queryresultaat, IQueryable<T>afgeleid van IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Zie de documentatie voor de verschillende LINQ-providers voor meer informatie over het maken van specifieke typen gegevensbronnen. De basisregel is echter eenvoudig: een LINQ-gegevensbron is een object dat ondersteuning biedt voor de algemene IEnumerable<T> interface of een interface die ervan overkomt, meestal IQueryable<T>.

Notitie

Typen zoals ArrayList die ondersteuning bieden voor de niet-algemene IEnumerable interface kunnen ook worden gebruikt als een LINQ-gegevensbron. Zie Een matrixlijst opvragen met LINQ (C#) voor meer informatie.

De query

De query geeft aan welke informatie moet worden opgehaald uit de gegevensbron of bronnen. Optioneel geeft een query ook op hoe die informatie moet worden gesorteerd, gegroepeerd en vormgegeven voordat deze wordt geretourneerd. Een query wordt opgeslagen in een queryvariabele en geïnitialiseerd met een query-expressie. U gebruikt de C#-querysyntaxis om query's te schrijven.

De query in het vorige voorbeeld retourneert alle even getallen uit de matrix met gehele getallen. De query-expressie bevat drie componenten: from, whereen select. (Als u bekend bent met SQL, hebt u gemerkt dat de volgorde van de componenten wordt omgekeerd van de volgorde in SQL.) De from component specificeert de gegevensbron, de where component past het filter toe en de select component geeft het type van de geretourneerde elementen op. Alle queryclausules worden in deze sectie uitgebreid besproken. Het belangrijkste punt is dat in LINQ de queryvariabele zelf geen actie onderneemt en geen gegevens retourneert. Hiermee worden alleen de gegevens opgeslagen die nodig zijn om de resultaten te produceren wanneer de query op een later tijdstip wordt uitgevoerd. Zie Overzicht van Standard-queryoperators (C#) voor meer informatie over hoe query's worden samengesteld.

Notitie

Query's kunnen ook worden uitgedrukt met behulp van de syntaxis van de methode. Zie Querysyntaxis en methodesyntaxis in LINQ voor meer informatie.

Classificatie van standaardqueryoperators op wijze van uitvoering

De IMPLEMENTATIEs van LINQ naar objecten van de standaardqueryoperatormethoden worden op een van de volgende twee manieren uitgevoerd: onmiddellijk of uitgesteld. De queryoperators die gebruikmaken van de uitgestelde uitvoering kunnen ook worden onderverdeeld in twee categorieën: streaming en niet-streamen.

Direct

Onmiddellijke uitvoering betekent dat de gegevensbron wordt gelezen en dat de bewerking eenmaal wordt uitgevoerd. Alle standaardqueryoperators die een scalaire resultaten retourneren, worden onmiddellijk uitgevoerd. Voorbeelden van dergelijke query's zijnCount, Max, en FirstAverage. Deze methoden worden uitgevoerd zonder een expliciete foreach instructie, omdat de query zelf moet worden gebruikt foreach om een resultaat te retourneren. Deze query's retourneren één waarde, geen IEnumerable verzameling. U kunt afdwingen dat elke query onmiddellijk wordt uitgevoerd met behulp van de Enumerable.ToList of Enumerable.ToArray methoden. Onmiddellijke uitvoering biedt hergebruik van queryresultaten, niet van querydeclaratie. De resultaten worden eenmaal opgehaald en vervolgens opgeslagen voor toekomstig gebruik. De volgende query retourneert een telling van de even getallen in de bronmatrix:

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

int evenNumCount = evenNumQuery.Count();

Als u de onmiddellijke uitvoering van een query wilt afdwingen en de resultaten in de cache wilt opslaan, kunt u de ToList of ToArray methoden aanroepen.

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

U kunt de uitvoering ook afdwingen door de foreach lus direct na de query-expressie te plaatsen. Als u echter alle gegevens in één verzamelingsobject aanroept ToList of ToArray in de cache opgeslagen.

Uitgesteld

Uitgestelde uitvoering betekent dat de bewerking niet wordt uitgevoerd op het punt in de code waarin de query wordt gedeclareerd. De bewerking wordt alleen uitgevoerd wanneer de queryvariabele wordt geïnventariseerd, bijvoorbeeld met behulp van een foreach instructie. De resultaten van het uitvoeren van de query zijn afhankelijk van de inhoud van de gegevensbron wanneer de query wordt uitgevoerd in plaats van wanneer de query is gedefinieerd. Als de queryvariabele meerdere keren wordt geïnventariseerd, kunnen de resultaten elke keer verschillen. Bijna alle standaardqueryoperators waarvan het retourtype is IEnumerable<T> of IOrderedEnumerable<TElement> op een uitgestelde manier wordt uitgevoerd. Uitgestelde uitvoering biedt de mogelijkheid om query's opnieuw te gebruiken, omdat de query de bijgewerkte gegevens uit de gegevensbron ophaalt telkens wanneer queryresultaten worden cursoreerd. De volgende code toont een voorbeeld van uitgestelde uitvoering:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

De foreach instructie is ook waar de queryresultaten worden opgehaald. In de vorige query bevat de iteratievariabele num bijvoorbeeld elke waarde (één voor één) in de geretourneerde reeks.

Omdat de queryvariabele zelf nooit de queryresultaten bevat, kunt u deze herhaaldelijk uitvoeren om bijgewerkte gegevens op te halen. Een afzonderlijke toepassing kan bijvoorbeeld een database voortdurend bijwerken. In uw toepassing kunt u één query maken waarmee de meest recente gegevens worden opgehaald en kunt u deze met intervallen uitvoeren om bijgewerkte resultaten op te halen.

Queryoperators die gebruikmaken van de uitgestelde uitvoering, kunnen ook worden geclassificeerd als streaming of niet-streamen.

Streaming

Streamingoperators hoeven niet alle brongegevens te lezen voordat ze elementen opleveren. Op het moment van uitvoering voert een streaming-operator de bewerking uit op elk bronelement terwijl het wordt gelezen en levert het element indien nodig op. Een streamingoperator blijft bronelementen lezen totdat een resultaatelement kan worden geproduceerd. Dit betekent dat meer dan één bronelement kan worden gelezen om één resultaatelement te produceren.

Niet-stroomopwaarts

Niet-stroomopwaartse operators moeten alle brongegevens lezen voordat ze een resultaatelement kunnen opleveren. Bewerkingen zoals sorteren of groeperen vallen in deze categorie. Op het moment van uitvoering lezen niet-stroomopwaartse queryoperators alle brongegevens, plaatst u deze in een gegevensstructuur, voert u de bewerking uit en levert u de resulterende elementen op.

Classificatietabel

De volgende tabel classificeert elke standaardqueryoperatormethode op basis van de uitvoeringsmethode.

Notitie

Als een operator in twee kolommen is gemarkeerd, zijn er twee invoerreeksen betrokken bij de bewerking en wordt elke reeks anders geëvalueerd. In deze gevallen is het altijd de eerste reeks in de parameterlijst die op een uitgestelde streaming-manier wordt geëvalueerd.

Standaardqueryoperator Retourtype Onmiddellijke uitvoering Uitgestelde streaming-uitvoering Uitgestelde niet-stroomopwaartse uitvoering
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Enkele numerieke waarde X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Eén numerieke waarde, TSourceof TResult? X
Min Eén numerieke waarde, TSourceof TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Enkele numerieke waarde X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource[] Array X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

LINQ naar objecten

LinQ naar objecten verwijst naar het gebruik van LINQ-query's met een IEnumerable of IEnumerable<T> meer verzamelingen rechtstreeks. U kunt LINQ gebruiken om query's uit te voeren op alle inventariserbare verzamelingen, zoals List<T>, Arrayof Dictionary<TKey,TValue>. De verzameling kan door de gebruiker worden gedefinieerd of een type dat wordt geretourneerd door een .NET-API. In de LINQ-benadering schrijft u declaratieve code die beschrijft wat u wilt ophalen. LINQ to Objects biedt een geweldige inleiding tot programmeren met LINQ.

LINQ-query's bieden drie belangrijke voordelen ten opzichte van traditionele foreach lussen:

  • Ze zijn beknopter en leesbaar, vooral bij het filteren van meerdere voorwaarden.
  • Ze bieden krachtige filter-, volgorde- en groeperingsmogelijkheden met een minimum aan toepassingscode.
  • Ze kunnen met weinig of geen wijzigingen worden overgezet naar andere gegevensbronnen.

Hoe complexer de bewerking die u wilt uitvoeren op de gegevens, hoe meer voordeel u realiseert met behulp van LINQ in plaats van traditionele iteratietechnieken.

De resultaten van een query opslaan in het geheugen

Een query is in feite een set instructies voor het ophalen en organiseren van gegevens. Query's worden lazily uitgevoerd, omdat elk volgend item in het resultaat wordt aangevraagd. Wanneer u foreach de resultaten wilt herhalen, worden items geretourneerd als geopend. Als u een query wilt evalueren en de resultaten wilt opslaan zonder een foreach lus uit te voeren, roept u een van de volgende methoden aan voor de queryvariabele:

U moet het geretourneerde verzamelingsobject toewijzen aan een nieuwe variabele wanneer u de queryresultaten opslaat, zoals wordt weergegeven in het volgende voorbeeld:

List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];

IEnumerable<int> queryFactorsOfFour =
    from num in numbers
    where num % 4 == 0
    select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

Zie ook