Dela via


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. Utför sökfrå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 illustration visar den fullständiga frågeoperationen. I LINQ är körningen av sökfrågan skild från sökfrågan själv. 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 den 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. LINQ till XML läser till exempel in ett XML-dokument till en frågebar typ XElement.

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

Med EntityFrameworkskapar du en objektrelationsmappning mellan C#-klasser och databasschemat. Du skriver dina frågor mot objekten, och under körning hanterar Entity Framework kommunikationen med databasen. I följande exempel representerar Customers 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 den, vanligtvis IQueryable<T>.

Anmärkning

Typer som ArrayList som stöder det icke-generiska IEnumerable-gränssnittet kan också användas som LINQ-datakälla. För mer information, se Utföra frågor mot en ArrayList 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.) Satsen from anger datakällan, where-satsen tillämpar filtret och select-satsen anger typen av returnerade element. 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 standardfrågaoperatorer (C#).

Anmärkning

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

Klassificering av standardfrågeoperatorer efter sättet för utförande

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

Omedelbar

Omedelbar körning innebär att datakällan läses, 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ända foreach för att returnera ett resultat. Dessa frågor returnerar ett enda värde, inte en IEnumerable samling. Du kan tvinga en fråga att köras omedelbart med hjälp av metoderna Enumerable.ToList 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 metoderna ToList 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å tvinga exekvering genom att placera foreach-loopen direkt efter frågeuttrycket. Men genom att anropa ToList eller ToArray cachelagrade du även alla data i ett enda samlingsobjekt.

Uppskjuten

Uppskjuten körning innebär att operationen inte utförs vid den punkt i koden där sökfrå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 med fördröjning. Fördröjd exekvering möjliggör återanvändning av frågor eftersom frågan hämtar den uppdaterade datan från datakällan varje gång frågeresultaten itereras. Följande kod visar ett exempel på uppskjuten exekvering.

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

Det är också i foreach-satsen som 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 den med jämna mellanrum för att hämta uppdaterade resultat.

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

Streaming

Strömningsoperatorer behöver inte läsa alla källdata innan de ger element. Vid körningsögonblicket utför en strömningsoperator sin åtgärd på varje källelement när det läses och producerar elementet om det är lämpligt. 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-strömmande

Icke-strömmande operatorer måste läsa alla källdata innan de kan ge ett resultat. Åtgärder som sortering eller gruppering tillhör den här kategorin. Vid exekvering läser icke-strömmande frågeoperatorer alla källdata, placerar dem i en datastruktur, utför operationen och returnerar de resulterande elementen.

Klassificeringstabell

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

Anmärkning

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 verkställande Uppskjuten strömningsexekvering Uppskjuten körning utan streaming
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average Enkelt numeriskt värde
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max Enkelt numeriskt värde, TSourceeller TResult?
Min Enkelt numeriskt värde, TSourceeller TResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum Enkelt numeriskt värde
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] array
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ till objekt

"LINQ to Objects" avser användningen av LINQ-frågor direkt med vilken som helst IEnumerable eller IEnumerable<T> samling. 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. Förfrågningar utförs först när det behövs, eftersom varje efterföljande objekt i resultatet begärs. När du använder foreach för att iterera resultaten returneras objekt i den ordning de nås. 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