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:
- Haal de gegevensbron op.
- Maak de query.
- 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.
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
, where
en 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 First
Average
. 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.
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]);