Grundlagen zu Abfrageausdrücken

Was ist eine Abfrage, und welche Funktion hat sie?

Eine Abfrage ist ein Satz von Anweisungen, der beschreibt, welche Daten aus einer bestimmten Datenquelle (oder Quellen) abgerufen werden sollen, und welche Form und Organisation die zurückgegebenen Daten haben sollen. Eine Abfrage unterscheidet sich von den Ergebnissen, die sie erzeugt.

Im Allgemeinen werden die Quelldaten logisch als Sequenz von Elementen der gleichen Art organisiert. Eine SQL-Datenbanktabelle enthält z.B. eine Sequenz von Zeilen. In einer XML-Datei gibt es eine „Sequenz“ von XML-Elementen (auch wenn diese hierarchisch in einer Baumstruktur organisiert sind). Eine Auflistung im Arbeitsspeicher enthält eine Sequenz von Objekten.

Aus Sicht einer Anwendung ist der spezifische Typ und die Struktur der ursprünglichen Datenquelle nicht wichtig. Die Anwendung interpretiert die Quelldaten immer als eine IEnumerable<T>- oder IQueryable<T>-Auflistung. In LINQ to XML werden die Quelldaten z.B. als ein IEnumerable<XElement> sichtbar gemacht.

Wenn diese Quellsequenz vorliegt, kann eine Abfrage eine der folgenden drei Aktionen durchführen:

  • Abrufen einer Teilmenge der Elemente zum Erstellen einer neuen Sequenz ohne die einzelnen Elemente zu verändern. Die Abfrage kann die zurückgegebenen Sequenzen dann auf verschiedene Arten sortieren oder gruppieren, wie im folgenden Beispiel gezeigt wird (Annahme: scores ist int[]):

    IEnumerable<int> highScoresQuery =
        from score in scores
        where score > 80
        orderby score descending
        select score;
    
  • Abrufen einer Sequenz von Elementen wie im vorherigen Beispiel, aber mit Transformation der Elemente in einen neuen Objekttyp. Eine Abfrage kann z.B. nur die Nachnamen aus bestimmten Kundendatensätzen in einer Datenquelle abrufen. Sie kann möglicherweise auch den vollständigen Datensatz abrufen und ihn zum Erstellen eines anderen Objekttyps im Arbeitsspeicher oder sogar XML-Daten vor dem Generieren der endgültigen Ergebnissequenz verwenden. Im folgenden Beispiel wird eine Projektion von int in string veranschaulicht. Beachten Sie den neuen Typ von highScoresQuery.

    IEnumerable<string> highScoresQuery2 =
        from score in scores
        where score > 80
        orderby score descending
        select String.Format("The score is {0}", score);
    
  • Abrufen eines Singleton-Werts zu den Quelldaten, z.B.:

    • Die Anzahl der Elemente, die eine bestimmte Bedingung erfüllen

    • Das Element, das den größten oder den niedrigsten Wert hat

    • Das erste Element, das einer Bedingung entspricht oder die Summe bestimmter Werte in einer angegebenen Menge von Elementen Die folgende Abfrage gibt z.B. die Anzahl von Ergebnissen aus dem scores-Ganzzahlarray zurück, die höher als 80 sind:

    int highScoreCount =
        (from score in scores
         where score > 80
         select score)
         .Count();
    

    Beachten Sie im vorherigen Beispiel die Verwendung von Klammern um den Abfrageausdruck vor dem Aufruf der Count-Methode. Sie können dies auch mit einer neuen Variable ausdrücken, um das konkrete Ergebnis zu speichern. Diese Technik ist besser lesbar, da die Variablen, die die Abfrage speichern, von der Abfrage getrennt sind, die ein Ergebnis speichert.

    IEnumerable<int> highScoresQuery3 =
        from score in scores
        where score > 80
        select score;
    
    int scoreCount = highScoresQuery3.Count();
    

Im vorherigen Beispiel wird die Abfrage im Aufruf von Count ausgeführt, da Count die Ergebnisse durchlaufen muss, um die Anzahl der von highScoresQuery zurückgegebenen Elemente zu bestimmen.

Was ist ein Abfrageausdruck?

Ein Abfrageausdruck ist eine in der Abfragesyntax ausgedrückte Abfrage. Ein Abfrageausdruck ist ein erstklassiges Sprachkonstrukt. Er verhält sich wie jeder andere Ausdruck und kann in jedem Kontext verwendet werden, in dem ein C#-Ausdruck gültig ist. Ein Abfrageausdruck besteht aus einem Satz von in einer deklarativen Syntax geschriebenen Klauseln, ähnlich wie SQL oder XQuery. Jede Klausel umfasst wiederum einen oder mehrere C#-Ausdrücke. Diese Ausdrücke sind möglicherweise selbst Abfrageausdrücke oder enthalten einen Abfrageausdruck.

Ein Abfrageausdruck muss mit einer from-Klausel beginnen und mit einer select- oder group-Klausel enden. Zwischen der ersten from-Klausel und der letzten select- oder group-Klausel kann ein Abfrageausdruck eine oder mehrere der folgenden optionalen Klauseln enthalten: where, orderby, join, let und sogar zusätzliche from-Klauseln. Sie können auch das Schlüsselwort into verwenden, um zuzulassen, das das Ergebnis einer join- oder group-Klausel als Quelle für zusätzliche Abfrageklauseln im selben Abfrageausdruck dient.

Abfragevariable

In LINQ ist eine Abfragevariable eine beliebige Variable, die eine Abfrage anstatt des Ergebnisses einer Abfrage speichert. Genauer gesagt ist eine Abfragevariable immer ein Enumerable-Typ, der eine Sequenz von Elementen erzeugt, wenn er in einer foreach-Anweisung oder einem direkten Aufruf der IEnumerator.MoveNext-Methode durchlaufen wird.

Das folgende Codebeispiel zeigt einen einfachen Abfrageausdruck mit einer Datenquelle, einer Filtering-Klausel, einer Ordering-Klausel und ohne Transformationen der Quellelemente. Die Klausel select beendet die Abfrage.

static void Main()
{
    // Data source.
    int[] scores = { 90, 71, 82, 93, 75, 82 };

    // Query Expression.
    IEnumerable<int> scoreQuery = //query variable
        from score in scores //required
        where score > 80 // optional
        orderby score descending // optional
        select score; //must end with select or group

    // Execute the query to produce the results
    foreach (int testScore in scoreQuery)
    {
        Console.WriteLine(testScore);
    }                  
}
// Outputs: 93 90 82 82      

Im vorherigen Beispiel ist scoreQuery eine Abfragevariable, die manchmal auch einfach als Abfrage bezeichnet wird. Die Abfragevariable speichert keine tatsächlichen Ergebnisdaten, die in der foreach-Schleife erzeugt werden. Wenn die foreach-Anweisung ausgeführt wird, werden die Ergebnisse der Abfrage nicht über die Abfragevariable scoreQuery zurückgegeben. Stattdessen werden sie über die Iterationsvariable testScore zurückgegeben. Die scoreQuery-Variable kann in einer zweiten foreach-Schleife durchlaufen werden. Die gleichen Ergebnisse werden erzeugt, solange weder die Variable noch die Datenquelle geändert wurde.

Eine Abfragevariable kann eine Abfrage speichern, die in einer Abfragesyntax oder Methodensyntax oder einer Kombination aus beiden ausgedrückt wird. In den folgenden Beispielen sind sowohl queryMajorCities als auch queryMajorCities2 Abfragevariablen:

//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;


// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

Andererseits zeigen die beiden nächsten Beispiele Variablen, die keine Abfragevariablen sind, obwohl beide mit einer Abfrage initialisiert werden. Sie sind keine Abfragevariablen, da sie Ergebnisse speichern:

int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();
// the following returns the same result
int highScore = scores.Max();

List<City> largeCitiesList =
    (from country in countries
     from city in country.Cities
     where city.Population > 10000
     select city)
       .ToList();

// or split the expression
IEnumerable<City> largeCitiesQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

Weitere Informationen zu den verschiedenen Verfahren zum Ausdrücken von Abfragen finden Sie unter Query syntax and method syntax in LINQ (Abfragesyntax und Methodensyntax in LINQ).

Explizite und implizite Typisierung von Abfragevariablen

Diese Dokumentation enthält normalerweise den expliziten Typ der Abfragevariablen, um die Typbeziehung zwischen der Abfrage und der select-Klausel darzustellen. Sie können aber auch das Schlüsselwort var verwenden, um den Compiler anzuweisen, den Typ einer Abfragevariable (oder eine andere lokale Variable) zur Kompilierzeit abzuleiten. Das Beispiel einer Abfrage, das vorher in diesem Thema gezeigt wurde, kann beispielsweise auch durch implizierte Typisierung ausgedrückt werden:

// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as 
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

Weitere Informationen finden Sie unter Implizit typisierte lokale Variablen und Type relationships in LINQ query operations (Typbeziehungen in LINQ-Abfragevorgängen).

Starten eines Abfrageausdrucks

Ein Abfrageausdruck muss mit einer from-Klausel beginnen. Er gibt eine Datenquelle zusammen mit einer Bereichsvariablen an. Die Bereichsvariable stellt jedes darauffolgende Element in der Quellsequenz dar, wenn das Quellelement durchsucht wird. Die Bereichsvariable ist, basierend auf den Typen des Elements in der Datenquelle, stark typisiert. Im folgenden Beispiel ist die Bereichsvariable auch als Country typisiert, da countries ein Array von Country-Objekten ist. Da die Bereichsvariable stark typisiert ist, können Sie den Punktoperator verwenden, um auf verfügbare Member des Typs zuzugreifen.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;

Die Bereichsvariable befindet sich im Geltungsbereich, bis die Abfrage entweder mit einem Semikolon oder einer continuation-Klausel beendet wird.

Ein Abfrageausdruck enthält möglicherweise mehrere from-Klauseln. Verwenden Sie zusätzliche from-Klauseln, wenn jedes Element in der Quellsequenz selbst eine Auflistung ist oder eine Auflistung enthält. Nehmen wir beispielsweise an, dass Sie über eine Auflistung von Country-Objekten verfügen, von der jedes eine Auflistung von City-Objekten mit dem Namen Cities enthält. Verwenden Sie zwei from-Klauseln, um die City-Objekte in jedem Country abzufragen, wie hier gezeigt:

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

Weitere Informationen finden Sie unter from-Klausel.

Beenden eines Abfrageausdrucks

Ein Abfrageausdruck muss entweder mit einer group- oder einer select-Klausel enden.

group-Klausel

Verwenden Sie die group-Klausel, um eine Sequenz von Gruppen zu erzeugen, die von einem von Ihnen angegebenen Schüssel organisiert wird. Der Schlüssel kann ein beliebiger Datentyp sein. Die folgende Abfrage erstellt z.B. eine Sequenz von Gruppen, die ein oder mehrere Country-Objekte enthält und deren Schlüssel ein char-Wert ist.

var queryCountryGroups =
    from country in countries
    group country by country.Name[0];

Weitere Informationen zum Gruppieren finden Sie unter group-Klausel.

select-Klausel

Verwenden Sie die select-Klausel, um alle anderen Typen von Sequenzen zu erzeugen. Eine einfache select-Klausel erzeugt nur eine Sequenz von Objekten desselben Typs wie die Objekte, die in der Datenquelle enthalten sind. In diesem Beispiel enthält die Datenquelle Country-Objekte. Die orderby-Klausel sortiert die Elemente in eine neue Reihenfolge, und die select-Klausel erzeugt eine Sequenz der neu angeordneten Country-Objekte.

IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

Die select-Klausel kann zum Transformieren von Quelldaten in Sequenzen neuer Typen verwendet werden. Diese Transformation wird auch als Projektion bezeichnet. Im folgenden Beispiel projiziertselect die-Klausel eine Sequenz anonymer Typen, die nur eine Teilmenge der Felder im originalen Element enthalten. Beachten Sie, dass die neuen Objekte mit einem Objektinitialisierer initialisiert werden.

// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };

Weitere Informationen zu allen Verfahren, in denen eine select-Klausel zum Transformieren von Daten verwendet werden kann, finden Sie unter select-Klausel.

Fortsetzungen mit „into“

Sie können das Schlüsselwort into in einer select- oder group-Klausel verwenden, um einen temporären Bezeichner zu erstellen, der eine Abfrage speichert. Dieses Vorgehen ist ratsam, wenn Sie zusätzliche Abfragevorgänge nach einem grouping- oder einem select-Vorgang auf eine Abfrage ausführen müssen. Im folgenden Beispiel werden countries gemäß der Bevölkerung in Bereiche von 10 Millionen gruppiert. Nachdem diese Gruppen erstellt wurden, filtern zusätzliche Klauseln einige Gruppen heraus und sortieren die Gruppen dann in aufsteigender Reihenfolge. Um diese zusätzlichen Vorgänge durchzuführen, wird die von countryGroup dargestellte Fortsetzung benötigt.

// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10_000_000
    group country by percentile into countryGroup
    where countryGroup.Key >= 20
    orderby countryGroup.Key
    select countryGroup;

// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
    Console.WriteLine(grouping.Key);
    foreach (var country in grouping)
        Console.WriteLine(country.Name + ":" + country.Population);
}

Weitere Informationen finden Sie unter into.

Filtern, Sortieren und Verknüpfen

Zwischen der from-Klausel am Anfang und der select- oder group-Klausel am Ende sind alle anderen Klauseln (where, join, orderby, from, let) optional. Eine der optionalen Klauseln kann entweder überhaupt nicht oder mehrfach in einem Abfragetext verwendet werden.

where-Klausel

Verwenden Sie die Klausel where zum Herausfiltern von Elementen aus den Quelldaten basierend auf einem oder mehreren Prädikatausdrücken. Im folgenden Beispiel verfügt die Klausel where über ein Prädikat mit zwei Bedingungen.

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population < 200000 && city.Population > 100000
    select city;

Weitere Informationen finden Sie unter where-Klausel.

orderby-Klausel

Verwenden Sie die orderby-Klausel zum Sortieren der Ergebnisse in auf- oder absteigender Reihenfolge. Sie können auch eine sekundäre Sortierreihenfolge angeben. Im folgenden Beispiel wird mit der Eigenschaft Area eine primäre Sortierung der country-Objekte durchgeführt. Anschließend wird eine sekundäre Sortierung mit der Eigenschaft Population durchgeführt.

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending
    select country;

Das Schlüsselwort ascending ist optional; wenn keine andere Reihenfolge angegeben ist, ist dies die Standardreihenfolge. Weitere Informationen finden Sie unter orderby-Klausel.

join-Klausel

Verwenden Sie die join-Klausel zum Zuordnen und/oder Kombinieren von Elementen aus einer Datenquelle mit Elementen aus einer anderen Datenquelle basierend auf einem Gleichheitsvergleich zwischen angegebenen Schlüsseln in jedem Element. In LINQ werden Verknüpfungsvorgänge für Sequenzen von Objekten ausgeführt, deren Elemente unterschiedliche Typen haben. Nachdem Sie zwei Segmente verknüpft haben, müssen Sie eine select- oder group-Anweisung verwenden, um anzugeben, welches Element in der Ausgabesequenz gespeichert werden soll. Sie können auch einen anonymen Typ verwenden, um Eigenschaften aus jedem Satz der zugewiesenen Elemente in einem neuen Typ für die Ausgabesequenz zu kombinieren. Im folgenden Beispiel werden prod-Objekte, deren Category-Eigenschaft einer der Kategorien im categories-Zeichenfolgearray entspricht, zugewiesen. Produkte, deren Category keiner Zeichenfolge in categories entspricht, werden herausgefiltert. Die select-Anweisung projiziert einen neuen Typ, dessen Eigenschaften aus cat und prod stammen.

var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new { Category = cat, Name = prod.Name };

Sie können auch eine Gruppenverknüpfung durchführen, indem Sie die Ergebnisse des join-Vorgangs mithilfe des Schlüsselworts into in eine temporäre Variable speichern. Weitere Informationen finden Sie unter join-Klausel.

let-Klausel

Verwenden Sie die let-Klausel zum Speichern der Ergebnisse eines Ausdrucks, z.B. eines Methodenaufrufs, in einer neuen Bereichsvariable. Im folgenden Beispiel speichert die Bereichsvariable firstName das erste Elemente eines Arrays von Zeichenfolgen, das von Split zurückgegeben wird.

string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
    from name in names
    let firstName = name.Split(' ')[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

Weitere Informationen finden Sie unter let-Klausel.

Unterabfragen in einem Abfrageausdruck

Ein Abfrageausdruck selbst kann eine Abfrageklausel enthalten, die manchmal als Unterabfrage bezeichnet wird. Jede Unterabfrage beginnt mit ihrer eigenen from-Klausel, die nicht unbedingt auf die gleiche Datenquelle in der ersten from-Klausel zeigt. Die folgende Abfrage zeigt z.B. einen Abfrageausdruck, der in der Select-Anweisung zum Abrufen der Ergebnisse eines Gruppierungsvorganges verwendet wird.

var queryGroupMax =
    from student in students
    group student by student.GradeLevel into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore =
            (from student2 in studentGroup
             select student2.Scores.Average())
             .Max()
    };

Weitere Informationen finden Sie unter How to: perform a subquery on a grouping operation (Vorgehensweise: Ausführen einer Unterabfrage für einen Gruppierungsvorgang).

Siehe auch

C#-Programmierhandbuch
LINQ query expressions (LINQ-Abfrageausdrücke)
Query keywords (LINQ) (Abfrageschlüsselwörter (LINQ))
Standard Query Operators Overview (C#) (Übersicht über Standardabfrageoperatoren (C#))