Introduktion till LINQ-frågor i C#

En fråga är ett uttryck som hämtar data från en datakälla. Olika datakällor har olika inbyggda frågespråk, till exempel SQL för relationsdatabaser och XQuery för XML. Utvecklare måste lära sig ett nytt frågespråk för varje typ av datakälla eller dataformat som de måste ha stöd för. LINQ förenklar den här situationen genom att erbjuda en konsekvent C#-språkmodell för olika typer av datakällor och format. I en LINQ-fråga arbetar du alltid med C#-objekt. Du använder samma grundläggande kodningsmönster för att fråga och transformera data i XML-dokument, SQL-databaser, .NET-samlingar och andra format när en LINQ-provider är tillgänglig.

Tre delar av en frågeåtgärd

Alla LINQ-frågeåtgärder består av tre distinkta åtgärder:

  1. Hämta datakällan.
  2. Skapa frågan.
  3. Kör frågan.

I följande exempel visas hur de tre delarna i en frågeåtgärd uttrycks i källkoden. I exemplet används en heltalsmatris som datakälla för enkelhetens skull. Samma begrepp gäller dock även för andra datakällor. Det här exemplet refereras till under resten av den här artikeln.

// 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);
}

Följande bild visar den fullständiga frågeåtgärden. I LINQ skiljer sig körningen av frågan från själva frågan. Med andra ord hämtar du inga data genom att skapa en frågevariabel.

Diagram över den fullständiga LINQ-frågeåtgärden.

Datakällan

Datakällan i föregående exempel är en matris som stöder det allmänna IEnumerable<T> gränssnittet. Det innebär att det kan frågas med LINQ. En fråga körs i en foreach -instruktion och foreach kräver IEnumerable eller IEnumerable<T>. Typer som stöder IEnumerable<T> eller ett härlett gränssnitt, till exempel det generiska IQueryable<T> , kallas frågebara typer.

En frågetyp kräver ingen ändring eller särskild behandling för att fungera som en LINQ-datakälla. Om källdata inte redan finns i minnet som en frågebar typ måste LINQ-providern representera dem som sådana. Till exempel läser LINQ till XML in ett XML-dokument till en frågebar XElement typ:

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

Med EntityFramework skapar du en objektrelationsmappning mellan C#-klasser och ditt databasschema. Du skriver dina frågor mot objekten och vid körning hanterar EntityFramework kommunikationen med databasen. I följande exempel Customers representerar en specifik tabell i databasen och typen av frågeresultat, IQueryable<T>, härleds från 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;

Mer information om hur du skapar specifika typer av datakällor finns i dokumentationen för de olika LINQ-leverantörerna. Den grundläggande regeln är dock enkel: en LINQ-datakälla är ett objekt som stöder det allmänna IEnumerable<T> gränssnittet, eller ett gränssnitt som ärver från det, vanligtvis IQueryable<T>.

Kommentar

Typer som ArrayList dessa stöder det icke-generiska IEnumerable gränssnittet kan också användas som en LINQ-datakälla. Mer information finns i Köra frågor mot en matrislista med LINQ (C#).

Frågan

Frågan anger vilken information som ska hämtas från datakällan eller källorna. En fråga anger också hur informationen ska sorteras, grupperas och formas innan den returneras. En fråga lagras i en frågevariabel och initieras med ett frågeuttryck. Du använder C#-frågesyntax för att skriva frågor.

Frågan i föregående exempel returnerar alla jämna tal från heltalsmatrisen. Frågeuttrycket innehåller tre satser: from, whereoch select. (Om du är bekant med SQL har du märkt att ordningen på satserna är omvänd från ordningen i SQL.) from Satsen anger datakällan, where satsen tillämpar filtret och select satsen anger typen av de returnerade elementen. Alla frågesatser beskrivs i detalj i det här avsnittet. För tillfället är det viktigt att frågevariabeln i LINQ inte vidtar någon åtgärd och returnerar inga data. Den lagrar bara den information som krävs för att generera resultatet när frågan körs någon gång senare. Mer information om hur frågor konstrueras finns i Översikt över vanliga frågeoperatorer (C#).

Kommentar

Frågor kan också uttryckas med hjälp av metodsyntax. Mer information finns i Frågesyntax och metodsyntax i LINQ.

Klassificering av standardfrågeoperatorer genom körning

LINQ-till-objekt-implementeringarna av standardmetoderna för frågeoperatorer körs på något av två huvudsakliga sätt: omedelbart eller uppskjutet. Frågeoperatorerna som använder uppskjuten körning kan dessutom delas in i två kategorier: direktuppspelning och icke-streaming.

Omedelbara

Omedelbar körning innebär att datakällan läse och att åtgärden utförs en gång. Alla standardfrågeoperatorer som returnerar ett skalärt resultat körs omedelbart. Exempel på sådana frågor är Count, Max, Averageoch First. Dessa metoder körs utan en explicit foreach instruktion eftersom själva frågan måste användas foreach för att returnera ett resultat. Dessa frågor returnerar ett enda värde, inte en IEnumerable samling. Du kan tvinga alla frågor att köras omedelbart med hjälp av Enumerable.ToList metoderna eller Enumerable.ToArray . Omedelbar körning ger återanvändning av frågeresultat, inte frågedeklaration. Resultaten hämtas en gång och lagras sedan för framtida användning. Följande fråga returnerar ett antal jämna tal i källmatrisen:

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

int evenNumCount = evenNumQuery.Count();

Om du vill framtvinga omedelbar körning av frågor och cachelagra dess resultat kan du anropa ToList metoderna eller ToArray .

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

Du kan också framtvinga körning genom att placera loopen foreach omedelbart efter frågeuttrycket. Men genom att anropa ToList eller ToArray cachelagrade du även alla data i ett enda samlingsobjekt.

Uppskjutet

Uppskjuten körning innebär att åtgärden inte utförs vid den punkt i koden där frågan deklareras. Åtgärden utförs endast när frågevariabeln räknas upp, till exempel med hjälp av en foreach -instruktion. Resultatet av att köra frågan beror på innehållet i datakällan när frågan körs i stället för när frågan har definierats. Om frågevariabeln räknas upp flera gånger kan resultatet variera varje gång. Nästan alla standardfrågeoperatorer vars returtyp är IEnumerable<T> eller IOrderedEnumerable<TElement> körs på ett uppskjutet sätt. Uppskjuten körning ger möjlighet till återanvändning av frågor eftersom frågan hämtar uppdaterade data från datakällan varje gång frågeresultat itereras. Följande kod visar ett exempel på uppskjuten körning:

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

Instruktionen foreach är också där frågeresultatet hämtas. I den föregående frågan innehåller till exempel iterationsvariabeln num varje värde (ett i taget) i den returnerade sekvensen.

Eftersom själva frågevariabeln aldrig innehåller frågeresultatet kan du köra den upprepade gånger för att hämta uppdaterade data. Ett separat program kan till exempel uppdatera en databas kontinuerligt. I ditt program kan du skapa en fråga som hämtar de senaste data och du kan köra dem med jämna mellanrum för att hämta uppdaterade resultat.

Frågeoperatorer som använder uppskjuten körning kan dessutom klassificeras som direktuppspelning eller icke-strömmande.

Strömning

Strömningsoperatorer behöver inte läsa alla källdata innan de ger element. Vid tidpunkten för körningen utför en strömningsoperator sin åtgärd på varje källelement när det läse och ger elementet vid behov. En strömningsoperator fortsätter att läsa källelement tills ett resultatelement kan skapas. Det innebär att mer än ett källelement kan läsas för att skapa ett resultatelement.

Icke-överordnade

Icke-överordnade operatorer måste läsa alla källdata innan de kan ge ett resultatelement. Åtgärder som sortering eller gruppering tillhör den här kategorin. Vid tidpunkten för körningen läser icke-överordnade frågeoperatorer alla källdata, placerar dem i en datastruktur, utför åtgärden och ger de resulterande elementen.

Klassificeringstabell

I följande tabell klassificeras varje standardmetod för frågeoperatorer enligt dess körningsmetod.

Kommentar

Om en operator är markerad i två kolumner är två indatasekvenser inblandade i åtgärden och varje sekvens utvärderas på olika sätt. I dessa fall är det alltid den första sekvensen i parameterlistan som utvärderas på ett uppskjutet, strömmande sätt.

Standard-frågeoperator Returtyp Omedelbar körning Uppskjuten strömningskörning Uppskjuten icke-streamingkörning
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Enkelt numeriskt värde 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 Enskilt numeriskt värde, TSource, eller TResult? X
Min Enskilt numeriskt värde, TSource, eller 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 Enkelt numeriskt värde 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 till objekt

"LINQ to Objects" avser användningen av LINQ-frågor med valfri IEnumerable eller IEnumerable<T> samling direkt. Du kan använda LINQ för att fråga alla uppräkningsbara samlingar, till exempel List<T>, Arrayeller Dictionary<TKey,TValue>. Samlingen kan vara användardefinierad eller en typ som returneras av ett .NET-API. I LINQ-metoden skriver du deklarativ kod som beskriver vad du vill hämta. LINQ to Objects ger en bra introduktion till programmering med LINQ.

LINQ-frågor erbjuder tre huvudsakliga fördelar jämfört med traditionella foreach loopar:

  • De är mer koncisa och läsbara, särskilt när du filtrerar flera villkor.
  • De tillhandahåller kraftfulla funktioner för filtrering, beställning och gruppering med ett minimum av programkod.
  • De kan portas till andra datakällor med liten eller ingen ändring.

Ju mer komplex åtgärden du vill utföra på data, desto mer nytta inser du att du använder LINQ i stället för traditionella iterationstekniker.

Lagra resultatet av en fråga i minnet

En fråga är i princip en uppsättning instruktioner för hur du hämtar och organiserar data. Frågor körs lazily, eftersom varje efterföljande objekt i resultatet begärs. När du använder foreach för att iterera resultaten returneras objekt som används. Om du vill utvärdera en fråga och lagra dess resultat utan att köra en foreach loop anropar du bara någon av följande metoder i frågevariabeln:

Du bör tilldela det returnerade samlingsobjektet till en ny variabel när du lagrar frågeresultaten, som du ser i följande exempel:

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

Se även