Freigeben über


Einführung in LINQ-Abfragen in C#

Eine Abfrage ist ein Ausdruck, der Daten aus einer Datenquelle abruft. Verschiedene Datenquellen weisen unterschiedliche systemeigene Abfragesprachen auf, z. B. SQL für relationale Datenbanken und XQuery für XML. Entwickler müssen eine neue Abfragesprache für jeden Datenquellen- oder Datenformattyp erlernen, den sie unterstützen müssen. LINQ vereinfacht diese Situation, indem ein einheitliches C#-Sprachmodell für Arten von Datenquellen und -formaten angeboten wird. In einer LINQ-Abfrage arbeiten Sie immer mit C#-Objekten. Sie verwenden dieselben grundlegenden Codierungsmuster, um Daten in XML-Dokumenten, SQL-Datenbanken, .NET-Auflistungen und anderen Formaten abzufragen und zu transformieren, wenn ein LINQ-Anbieter verfügbar ist.

Drei Teile eines Abfragevorgangs

Alle LINQ-Abfragevorgänge bestehen aus drei unterschiedlichen Aktionen:

  1. Rufen Sie die Datenquelle ab.
  2. Erstellen Sie die Abfrage.
  3. Führen Sie die Abfrage aus.

Das folgende Beispiel zeigt, wie die drei Teile eines Abfragevorgangs im Quellcode ausgedrückt werden. Im Beispiel wird ein ganzzahliges Array als Datenquelle zur Vereinfachung verwendet. Die gleichen Konzepte gelten jedoch auch für andere Datenquellen. Dieses Beispiel wird während des restlichen Artikels behandelt.

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

Die folgende Abbildung zeigt den vollständigen Abfragevorgang. In LINQ unterscheidet sich die Ausführung der Abfrage von der Abfrage selbst. Mit anderen Worten, Sie rufen keine Daten ab, indem Sie eine Abfragevariable erstellen.

Diagramm des vollständigen LINQ-Abfragevorgangs.

Die Datenquelle

Die Datenquelle im vorherigen Beispiel ist ein Array, das die generische IEnumerable<T> Schnittstelle unterstützt. Dies bedeutet, dass sie mit LINQ abgefragt werden kann. Eine Abfrage wird in einer foreach Anweisung ausgeführt und foreach erfordert IEnumerable oder IEnumerable<T>. Typen, die IEnumerable<T> oder eine abgeleitete Schnittstelle unterstützen, z. B. die generischen IQueryable<T>, werden als abfragbare Typen bezeichnet.

Ein abfragbarer Typ erfordert keine Änderung oder spezielle Behandlung als LINQ-Datenquelle. Wenn sich die Quelldaten noch nicht im Arbeitsspeicher als abfragbarer Typ befinden, muss der LINQ-Anbieter sie als solche darstellen. Beispielsweise lädt LINQ to XML ein XML-Dokument in einen abfragbaren XElement Typ:

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

Mit EntityFramework erstellen Sie eine objektrelationale Zuordnung zwischen C#-Klassen und Ihrem Datenbankschema. Sie schreiben Ihre Abfragen für die Objekte, und zur Laufzeit verarbeitet EntityFramework die Kommunikation mit der Datenbank. Im folgenden Beispiel stellt Customers eine bestimmte Tabelle in der Datenbank dar, und der Typ des Abfrageergebnisses, IQueryable<T>, wird von IEnumerable<T> abgeleitet.

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;

Weitere Informationen zum Erstellen bestimmter Arten von Datenquellen finden Sie in der Dokumentation für die verschiedenen LINQ-Anbieter. Die Grundregel ist jedoch einfach: Eine LINQ-Datenquelle ist jedes Objekt, das die generische IEnumerable<T> Schnittstelle unterstützt, oder eine Schnittstelle, die von ihr erbt, in der Regel IQueryable<T>.

Hinweis

Typen wie ArrayList, die die nicht generische IEnumerable Schnittstelle unterstützen, können auch als LINQ-Datenquelle verwendet werden. Weitere Informationen finden Sie unter Wie man eine ArrayList mit LINQ abfragt (C#).

Die Abfrage

Die Abfrage gibt an, welche Informationen aus der Datenquelle oder Quellen abgerufen werden sollen. Optional gibt eine Abfrage auch an, wie diese Informationen sortiert, gruppiert und geformt werden sollen, bevor sie zurückgegeben werden. Eine Abfrage wird in einer Abfragevariablen gespeichert und mit einem Abfrageausdruck initialisiert. Sie verwenden die C#-Abfragesyntax zum Schreiben von Abfragen.

Die Abfrage im vorherigen Beispiel gibt alle geraden Zahlen aus dem ganzzahligen Array zurück. Der Abfrageausdruck enthält drei Klauseln: from, , whereund select. (Wenn Sie mit SQL vertraut sind, haben Sie festgestellt, dass die Reihenfolge der Klauseln von der Reihenfolge in SQL umgekehrt wird.) Die from Klausel gibt die Datenquelle an, die where Klausel wendet den Filter an, und die select Klausel gibt den Typ der zurückgegebenen Elemente an. Alle Abfrageklauseln werden in diesem Abschnitt ausführlich erläutert. Der wichtige Punkt ist, dass in LINQ die Abfragevariable selbst keine Aktion ausführt und keine Daten zurückgibt. Es speichert nur die Informationen, die zum Erstellen der Ergebnisse erforderlich sind, wenn die Abfrage zu einem späteren Zeitpunkt ausgeführt wird. Weitere Informationen zum Erstellen von Abfragen finden Sie unter "Standard Query Operators Overview (C#)".

Hinweis

Abfragen können auch mithilfe der Methodensyntax ausgedrückt werden. Weitere Informationen finden Sie unter Abfragesyntax und Methodensyntax in LINQ.

Klassifizierung von Standardabfrageoperatoren nach Ausführung

Die LINQ to Objects-Implementierungen der Standardabfrageoperatormethoden werden auf eine von zwei Hauptmethoden ausgeführt: sofort oder verzögert. Die Abfrageoperatoren, die die verzögerte Ausführung verwenden, können zusätzlich in zwei Kategorien unterteilt werden: Streaming und Nichtstreaming.

Sofortig

Die sofortige Ausführung bedeutet, dass die Datenquelle gelesen wird und der Vorgang einmal ausgeführt wird. Alle Standardabfrageoperatoren, die ein skalares Ergebnis zurückgeben, werden sofort ausgeführt. Beispiele für solche Abfragen sind Count, , Max, Averageund First. Diese Methoden werden ohne eine explizite foreach Anweisung ausgeführt, da die Abfrage selbst das foreach verwenden muss, um ein Ergebnis zurückzugeben. Diese Abfragen geben einen einzelnen Wert und keine IEnumerable Auflistung zurück. Sie können erzwingen, dass jede Abfrage sofort mit den Enumerable.ToList Methoden oder Enumerable.ToArray Methoden ausgeführt wird. Die sofortige Ausführung ermöglicht die Wiederverwendung von Abfrageergebnissen und nicht der Abfragedeklaration. Die Ergebnisse werden einmal abgerufen und dann für die zukünftige Verwendung gespeichert. Die folgende Abfrage gibt die Anzahl der geraden Zahlen im Quellarray zurück:

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

int evenNumCount = evenNumQuery.Count();

Um die sofortige Ausführung einer Abfrage zu erzwingen und die Ergebnisse zwischenzuspeichern, können Sie die ToList Oder-Methoden ToArray aufrufen.

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

Sie können auch die Ausführung erzwingen, indem Sie die foreach Schleife unmittelbar nach dem Abfrageausdruck platzieren. Durch das Aufrufen von ToList oder ToArray werden alle Daten in einem einzigen Auflistungsobjekt zwischengespeichert.

Zurückgestellt

Verzögerte Ausführung bedeutet, dass der Vorgang nicht am Punkt im Code ausgeführt wird, an dem die Abfrage deklariert wird. Der Vorgang wird nur ausgeführt, wenn die Abfragevariable aufgezählt wird, z. B. mithilfe einer foreach Anweisung. Die Ergebnisse der Ausführung der Abfrage hängen vom Inhalt der Datenquelle ab, wenn die Abfrage ausgeführt wird und nicht, wenn die Abfrage definiert ist. Wenn die Abfragevariable mehrmals aufgezählt wird, können sich die Ergebnisse jedes Mal unterscheiden. Fast alle Standardabfrageoperatoren, deren Rückgabetyp IEnumerable<T> oder IOrderedEnumerable<TElement> ist, werden verzögert ausgeführt. Die verzögerte Ausführung ermöglicht die Wiederverwendung von Abfragen, da die Abfrage die aktualisierten Daten jedes Mal aus der Datenquelle abruft, wenn Abfrageergebnisse iteriert werden. Der folgende Code zeigt ein Beispiel für verzögerte Ausführung:

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

Die foreach Anweisung ist auch der Ort, an dem die Abfrageergebnisse abgerufen werden. In der vorherigen Abfrage enthält die Iterationsvariable num beispielsweise jeden Wert (jeweils einzeln) in der zurückgegebenen Sequenz.

Da die Abfragevariable selbst nie die Abfrageergebnisse enthält, können Sie sie wiederholt ausführen, um aktualisierte Daten abzurufen. Beispielsweise kann eine separate Anwendung eine Datenbank kontinuierlich aktualisieren. In Ihrer Anwendung können Sie eine Abfrage erstellen, die die neuesten Daten abruft, und Sie können sie in Intervallen ausführen, um aktualisierte Ergebnisse abzurufen.

Abfrageoperatoren, die verzögerte Ausführung verwenden, können zusätzlich als Streaming oder nichtstreaming klassifiziert werden.

Streamen

Streamingoperatoren müssen nicht alle Quelldaten lesen, bevor sie Elemente liefern. Zum Zeitpunkt der Ausführung führt ein Streamingoperator seinen Vorgang für jedes Quellelement aus, wie es gelesen wird, und gibt das Element bei Bedarf zurück. Ein Streamingoperator liest weiterhin Quellelemente, bis ein Ergebniselement erstellt werden kann. Dies bedeutet, dass mehrere Quellelemente gelesen werden können, um ein Ergebniselement zu erzeugen.

Nicht-Streaming

Nichtstreamingoperatoren müssen alle Quelldaten lesen, bevor sie ein Ergebniselement liefern können. Vorgänge wie Sortieren oder Gruppieren fallen in diese Kategorie. Zum Zeitpunkt der Ausführung lesen nicht streamende Abfrageoperatoren alle Quelldaten, fügen sie in eine Datenstruktur ein, führen den Vorgang aus und liefern die resultierenden Elemente.

Klassifizierungstabelle

In der folgenden Tabelle werden die einzelnen Standardabfrageoperatormethoden entsprechend ihrer Ausführungsmethode klassifiziert.

Hinweis

Wenn ein Operator in zwei Spalten markiert ist, sind zwei Eingabesequenzen an dem Vorgang beteiligt, und jede Sequenz wird unterschiedlich ausgewertet. In diesen Fällen ist es immer die erste Sequenz in der Parameterliste, die auf verzögerte, streaming-Weise ausgewertet wird.

Standardabfrageoperator Rückgabetyp Sofortige Ausführung Verzögerte Streamingausführung Zurückgestellte Nicht-Streaming-Ausführung
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average Einzelner numerischer Wert
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 Einzelner numerischer Wert, TSourceoder TResult?
Min Einzelner numerischer Wert, TSourceoder 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 Einzelner numerischer Wert
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 to Objects

„LINQ to Objects“ bezieht sich direkt auf die Verwendung von LINQ-Abfragen mit jeder IEnumerable- oder IEnumerable<T>-Sammlung. Sie können LINQ verwenden, um alle aufzählbaren Auflistungen abzufragen, wie z. B. List<T>, Array oder Dictionary<TKey,TValue>. Die Sammlung kann benutzerdefiniert sein oder ein von einer .NET-API zurückgegebener Typ. Im LINQ-Ansatz schreiben Sie deklarativen Code, der beschreibt, was Sie abrufen möchten. LINQ to Objects bietet eine großartige Einführung in die Programmierung mit LINQ.

LINQ-Abfragen bieten drei Hauptvorteile gegenüber herkömmlichen foreach Schleifen:

  • Sie sind präziser und lesbarer, insbesondere beim Filtern mehrerer Bedingungen.
  • Sie bieten leistungsstarke Filter-, Sortier- und Gruppierungsfunktionen mit einem Minimum an Anwendungscode.
  • Sie können mit wenig oder ohne Änderung zu anderen Datenquellen portiert werden.

Je komplexer der Vorgang, den Sie für die Daten ausführen möchten, desto besser profitieren Sie von der Verwendung von LINQ anstelle herkömmlicher Iterationstechniken.

Speichern der Ergebnisse einer Abfrage im Arbeitsspeicher

Eine Abfrage ist im Grunde eine Reihe von Anweisungen zum Abrufen und Organisieren von Daten. Abfragen werden träge ausgeführt, da jedes nachfolgende Element im Ergebnis angefordert wird. Wenn Sie foreach zum Durchlaufen der Ergebnisse verwenden, werden Elemente so zurückgegeben, wie auf sie zugegriffen wurde. Um eine Abfrage auszuwerten und die Ergebnisse zu speichern, ohne eine foreach Schleife auszuführen, rufen Sie einfach eine der folgenden Methoden für die Abfragevariable auf:

Sie sollten dem zurückgegebenen Auflistungsobjekt eine neue Variable zuweisen, wenn Sie die Abfrageergebnisse speichern, wie im folgenden Beispiel gezeigt:

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

Siehe auch