Einführung in LINQ-Abfragen (C#)

Eine Abfrage ist ein Ausdruck, der Daten von einer Datenquelle abruft. Abfragen werden normalerweise in einer spezialisierten Abfragesprache ausgedrückt. Im Laufe der Zeit wurden verschiedene Sprachen für die verschiedenen Datenquellen entwickelt, beispielsweise SQL für relationale Datenbanken und XQuery für XML. Aus diesem Grund mussten Entwickler 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 Modells zum Arbeiten mit Daten in verschiedenen Arten von Datenquellen und Formaten. In einer LINQ-Abfrage arbeiten Sie immer mit Objekten. Sie verwenden dieselben grundlegenden Codierungsmuster für die Abfrage und Transformation von Daten in XML-Dokumenten, SQL-Datenbanken, ADO.NET-Datasets, .NET-Auflistungen sowie allen anderen Quellen und Formaten, für die 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 Themas Bezug genommen.

class IntroToLINQ
{        
    static void Main()
    {
        // The Three Parts of a LINQ Query:
        //  1. Data source.
        int[] numbers = new int[7] { 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, oder anders ausgedrückt: Durch das bloße Erstellen einer Abfragevariablen werden keine Daten abgefragt.

Vollständiger LINQ-Abfragevorgang

Die Datenquelle

Da es sich bei der Datenquelle im vorherigen Beispiel um ein Array handelt, unterstützt sie implizit die generische IEnumerable<T>-Schnittstelle. Das 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> 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 solcher 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 LINQ to SQL erstellen Sie zuerst eine objektrelationale Zuordnung zur Entwurfszeit, entweder manuell oder mit den LING in SQL-Tools in Visual Studio. Sie schreiben die Abfragen anhand der Objekte, und zur Laufzeit übernimmt LINQ to SQL 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 sehr einfach: Eine LINQ-Datenquelle ist jedes Objekt, das die generische IEnumerable<T>-Schnittstelle oder eine Schnittstelle unterstützt, die davon erbt.

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. Um das Schreiben von Abfragen zu erleichtern, hat C# eine neue Abfragesyntax eingeführt.

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 umgekehrt wie in SQL 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. Diese und weitere Abfrageklauseln werden ausführlich im Abschnitt LINQ-Abfrageausdrücke 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 hinter den Kulissen 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

Wie bereits erwähnt, speichert die Abfragevariable selbst 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:

//  Query execution. 
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 beliebig oft ausführen. Sie könnten beispielsweise über eine Datenbank verfügen, die ständig durch eine separate Anwendung aktualisiert wird. Sie könnten in Ihrer Anwendung eine Abfrage erstellen, die die neuesten Daten abruft, und Sie könnten diese Abfrage in bestimmten Abständen wiederholt ausführen, um bei jeder Ausführung andere 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 Abfragen werden ohne explizite foreach-Anweisung ausgeführt, da die Abfrage selbst foreach verwenden muss, um ein Ergebnis auszugeben. Beachten Sie auch, dass diese Typen von Abfragen einen einzelnen Wert und keine IEnumerable-Auflistung zurückgeben. 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

Erste Schritte mit LINQ in C#
Exemplarische Vorgehensweise: Schreiben von Abfragen in C#
Exemplarische Vorgehensweise: Schreiben von Abfragen in C#
LINQ-Abfrageausdrücke
foreach, in
Abfrageschlüsselwörter (LINQ)