Einführung in LINQ-Abfragen (C#)

Eine Abfrage ist ein Ausdruck, der Daten von einer Datenquelle abruft. Verschiedene Datenquellen enthalten verschiedene native Abfragesprachen, beispielsweise SQL für relationale Datenbanken und XQuery für XML. Entwickler*innen müssen für jeden Typ von Datenquelle oder Datenformat, den sie unterstützen müssen, eine neue Abfragesprache erlernen. LINQ vereinfacht diese Situation durch die Bereitstellung eines konsistenten C#-Sprachmodells für verschiedene Arten von Datenquellen und Formaten. In einer LINQ-Abfrage arbeiten Sie immer mit C#-Objekten. Sie verwenden dieselben grundlegenden Codierungsmuster für die Abfrage und Transformation von Daten in XML-Dokumenten, SQL-Datenbanken, .NET-Auflistungen sowie allen anderen Formaten, wenn ein LINQ-Anbieter verfügbar ist.

Drei Teile einer Abfrageoperation

Alle LINQ-Abfrageoperationen bestehen aus drei unterschiedlichen Aktionen:

  1. Abrufen der Datenquelle
  2. Erstellen der Abfrage
  3. Ausführen der Abfrage

Im folgenden Beispiel wird gezeigt, wie die drei Teile einer Abfrageoperation in Quellcode ausgedrückt werden. Das Beispiel verwendet aus praktischen Gründen ein Array von Ganzzahlen als Datenquelle. Dieselben Konzepte gelten jedoch auch für andere Datenquellen. Auf dieses Beispiel wird im Rest dieses Artikels Bezug genommen.

// 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 die vollständige Abfrageoperation. In LINQ unterscheidet sich die Ausführung der Abfrage von der Abfrage selbst. Anders ausgedrückt: Sie rufen keine Daten ab, indem Sie eine Abfragevariable erstellen.

Diagram of the complete LINQ query operation.

Die Datenquelle

Bei der Datenquelle im vorherigen Beispiel handelt es sich um ein Array, das die generische IEnumerable<T>-Schnittstelle unterstützt. Das bedeutet, dass sie mit L abgefragt werden kann. Eine Abfrage wird in einer foreach-Anweisung ausgeführt, und foreach erfordert IEnumerable oder IEnumerable<T>. Typen, die IEnumerable<T> unterstützen oder eine abgeleitete Schnittstelle, wie z.B. der generische Typ IQueryable<T>, werden als abfragbare Typen bezeichnet.

Für abfragbare Typen ist keine Änderung oder besondere Behandlung notwendig, um sie als LINQ-Datenquelle zu verwenden. Wenn die Quelldaten nicht bereits als abfragbarer Typ im Arbeitsspeicher vorhanden sind, muss der LINQ-Anbieter diese als solchen darstellen. Zum Beispiel 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 die Abfragen für die Objekte, und zur Laufzeit übernimmt 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 Typen von Datenquellen finden Sie in der Dokumentation der verschiedenen LINQ-Anbieter. Die Grundregel ist jedoch einfach: Eine LINQ-Datenquelle ist jedes Objekt, das die generische IEnumerable<T>-Schnittstelle oder eine Schnittstelle unterstützt, die davon erbt (in der Regel IQueryable<T>).

Hinweis

Typen wie ArrayList, die die nicht generische IEnumerable-Schnittstelle unterstützen, können ebenso als LINQ-Datenquelle verwendet werden. Weitere Informationen finden Sie unter Vorgehensweise: Abfragen von ArrayList mit LINQ (C#).

Die Abfrage

Die Abfrage gibt an, welche Informationen aus der Datenquelle oder den Datenquellen abgerufen werden sollen. Optional kann eine Abfrage auch angeben, wie diese Informationen vor der Rückgabe sortiert, gruppiert und strukturiert werden sollen. 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 einem Ganzzahlen-Array zurück. Der Abfrageausdruck enthält drei Klauseln: from, where und select. (Wenn Sie mit SQL vertraut sind, ist Ihnen wahrscheinlich aufgefallen, dass die Klauseln im Vergleich zu SQL in umgekehrter Reihenfolge angeordnet sind.) 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 diese Abfrageklauseln werden ausführlich in diesem Abschnitt erläutert. Wichtig ist hier, dass die Abfragevariable selbst in LINQ keine Aktion ausführt und keine Daten zurückgibt. Sie speichert nur die Informationen, die erforderlich sind, um Ergebnisse zu erzeugen, wenn die Abfrage zu einem späteren Zeitpunkt ausgeführt wird. Weitere Informationen zum Erstellen von Abfragen finden Sie unter Übersicht über Standardabfrageoperatoren (C#).

Hinweis

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

Abfrageausführung

Verzögerte Ausführung

Die Abfragevariable selbst speichert nur die Abfragebefehle. Die tatsächliche Ausführung der Abfrage wird so lange verzögert, bis Sie die Abfragevariable in einer foreach-Anweisung durchlaufen. Dieses Konzept wird als verzögerte Ausführung bezeichnet und wird im folgenden Beispiel veranschaulicht:

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

In der foreach-Anweisung werden auch die Abfrageergebnisse abgerufen. So ist beispielsweise in der vorherigen Abfrage in der Iterationsvariablen num jeder Wert (jeweils einzeln) der zurückgegebenen Sequenz enthalten.

Da die Abfragevariable selbst nie die Abfrageergebnisse enthält, können Sie sie wiederholt ausführen, um aktualisierte Daten abzurufen. Sie verfügen beispielsweise über eine Datenbank, die ständig durch eine separate Anwendung aktualisiert wird. Sie können in Ihrer Anwendung eine Abfrage erstellen, die die neuesten Daten abruft, und Sie könnten diese Abfrage in Intervallen ausführen, um aktualisierte Ergebnisse abzurufen.

Erzwingen der unmittelbaren Ausführung

Abfragen, die Aggregationsfunktionen für einen Bereich von Quellelementen ausführen, müssen zuerst diese Elemente durchlaufen. Beispiele für solche Abfragen sind Count, Max, Average und First. Diese Methoden werden ohne explizite foreach-Anweisung ausgeführt, da die Abfrage selbst foreach verwenden muss, um ein Ergebnis zurückzugeben. Diese Abfragen geben einen einzelnen Wert und keine IEnumerable-Auflistung zurück. Die folgende Abfrage gibt eine 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 unmittelbare Ausführung einer Abfrage zu erzwingen und ihre Ergebnisse zwischenzuspeichern, können Sie die ToList-Methode oder die ToArray-Methode 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 die Ausführung auch erzwingen, indem Sie die foreach-Schleife unmittelbar nach dem Abfrageausdruck setzen. Durch Aufrufen von ToList oder ToArray speichern Sie jedoch auch alle Daten in einem einzelnen Auflistungsobjekt zwischen.

Siehe auch