Share via


Grundlagen zu Abfrageausdrücken (C#-Programmierhandbuch)

Was ist eine Abfrage und welche Aufgabe hat sie?

Eine Abfrage ist ein Satz an Anweisungen, die beschreiben, welche Daten aus angegebenen Datenquellen abgerufen werden und welche Form und Anordnung die zurückgegeben Daten haben sollten. Abfragen unterscheiden sich durch die erzielten Ergebnisse.

Im Allgemeinen werden die Quelldaten logisch als Sequenz von Elementen der gleichen Art organisiert. Eine SQL-Datenbanktabelle enthält eine Zeilensequenz. Dementsprechend enthält eine ADO.NETDataTable eine Sequenz an DataRow-Objekten. In einer XML-Datei gibt es eine "Sequenz" an XML-Elementen (auch wenn diese hierarchisch in einer Struktur angeordnet sind). Eine speicherinterne Auflistung enthält eine Sequenz von Objekten.

Vom Standpunkt einer Anwendung aus sind der spezifische Typ und die spezifische Struktur der ursprünglichen Quelldaten nicht wichtig. Die Anwendung interpretiert die Quelldaten immer als eine IEnumerable-Auflistung oder IQueryable-Auflistung. In LINQ to XML werden die Quelldaten als IEnumerable <XElement> sichtbar gemacht. In LINQ to DataSet ist es ein IEnumerable<DataRow>. In LINQ to SQL ist es ein IEnumerable oder IQueryable eines beliebigen benutzerdefinierten Objekts, das Sie definiert haben, um die Daten in der SQL-Tabelle darzustellen.

Mit dieser Quellsequenz kann eine Abfrage eine der drei folgenden Aufgaben ausführen:

  • Abrufen einer Teilmenge der Elemente, um eine neue Sequenz zu erzeugen, ohne die einzelnen Elemente zu ändern. Die Abfrage kann dann die zurückgegebene Sequenz auf unterschiedliche Weise sortieren und gruppieren, wie im folgenden Beispiel dargestellt 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, jedoch werden sie in einen neuen Objekttyp transformiert. Eine Abfrage ruft möglicherweise z. B. nur den Nachnamen aus bestimmten Kundendatensätzen in der Datenquelle ab. Oder sie ruft u. U. den vollständigen Datensatz ab und verwendet ihn dann zum Erstellen eines anderen speicherinternen Objekttyps oder sogar von XML-Daten, bevor die endgültige Ergebnissequenz erstellt wird. Im folgenden Beispiel wird eine Transformation von einer int zu einer 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 Singletonwerts zu Quelldaten, z. B.:

    • Die Anzahl an Elementen, die einer bestimmten Bedingung entsprechen.

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

    • Das erste Element, das einer Bedingung entspricht, oder die Summe bestimmter Werte in einem angegeben Satz von Elementen. Die folgende Abfrage gibt beispielsweise aus dem scores-Ganzzahlarray die Anzahl der Testergebnisse über 80 zurück:

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

    Beachten Sie im vorherigen Beispiel den Gebrauch von Klammern um den Abfrageausdruck vor dem Aufruf der Count-Methode. Sie können dies auch ausdrücken, indem Sie eine neue Variable verwenden, um das Endergebnis zu speichern. Diese Technik ist besser lesbar, da hierbei die Variable, die die Abfrage speichert, von der Abfrage, die ein Ergebnis speichert, unabhängig bleibt.

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

Im vorherigen Beispiel wird die Abfrage beim 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. Es entspricht im Grunde jedem anderen Ausdruck und kann in jedem beliebigen Kontext verwendet werden, in dem ein C#-Ausdruck gültig ist. Ein Abfrageausdruck besteht aus einem Klauselsatz, der ähnlich wie SQL oder XQuery in einer deklarativen Syntax geschrieben wird. Jede Klausel umfasst wiederum einen oder mehr C#-Ausdrücke, und bei diesen Ausdrücken kann es sich um einen Abfrageausdruck handeln, oder sie können einen Abfrageausdruck enthalten.

Ein Abfrageausdruck muss mit einer from-Klausel beginnen und mit einer select-Klausel oder einer group-Klausel enden. Zwischen der ersten from-Klausel und der letzten select-Klausel bzw. group-Klausel, können sich eine oder mehrere der folgenden optionalen Klauseln befinden: where, orderby, join, let sowie die zusätzlichen from-Klauseln. Sie können sogar das into-Schlüsselwort verwenden, um das Ergebnis einer join-Klausel oder group-Klausel zu aktivieren, um als Quelle für zusätzliche Abfrageklauseln im gleichen Abfrageausdruck zu fungieren.

Abfragevariable

In LINQ ist eine Abfragevariable eine beliebige Variable, die eine Abfrage anstelle der Ergebnisse einer Abfrage speichert. Das heißt, eine Abfragevariable ist immer ein Enumerable-Typ, der eine Elementsequenz erzeugt, wenn sie in einer foreach-Anweisung oder einem Direktaufruf der eigenen IEnumerator.MoveNext-Methode durchlaufen wird.

Das folgende Codebeispiel zeigt einen einfachen Abfrageausdruck mit einer Datenquelle, einer Filterklausel, einer Sortierklausel und ohne Transformationen der Quellelemente. Die select-Klausel 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 gelegentlich auch einfach nur als Abfrage bezeichnet wird. Die Abfragevariable speichert keine tatsächlichen Ergebnisdaten, die in der foreach-Schleife erzeugt werden. Und wenn die foreach-Anweisung ausgeführt wird, werden die Abfrageergebnisse nicht durch die scoreQuery-Abfragevariable zurückgegeben. Stattdessen werden sie durch die testScore-Iterationsvariable zurückgegeben. Die scoreQuery-Variable kann in einer zweiten foreach-Schleife durchlaufen werden. Es werden die gleichen Ergebnisse erzeugt, solange weder die Variable noch die Datenquelle geändert wurde.

Eine Abfragevariable kann eine Abfrage speichern, die in einer Abfrage- oder Methodensyntax bzw. einer Kombination der 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);

Die folgenden beiden Beispiele zeigen jedoch auch Variablen, die keine Abfragevariablen sind, obwohl alle durch eine 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();

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

Hinweis

In der LINQ-Dokumentation weisen Variablen, die eine Abfrage speichern, das Wort "query" als Teil des Namens auf.Variablen, die ein tatsächliches Ergebnis speichern, verfügen nicht über diesen Namenszusatz.

Weitere Informationen zu den verschiedenen Verfahren zum Ausdrücken von Abfragen finden Sie unter Abfragesyntax und Methodensyntax in LINQ (C#).

Explizite und implizite Typisierung von Abfragevariablen

Diese Dokumentation stellt normalerweise den expliziten Typ der Abfragevariablen bereit, um die Typbeziehung zwischen der Abfragevariable und der select-Klausel anzuzeigen. Sie können jedoch auch das var-Schlüsselwort verwenden, um den Compiler anzuweisen, den Typ einer Abfragevariablen (oder einer anderen lokalen Variablen) zum Zeitpunkt der Kompilierung abzuleiten. Das zuvor in diesem Thema gezeigte Abfragebeispiel kann beispielsweise auch durch eine implizite 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 (C#-Programmierhandbuch) und Typbeziehungen in LINQ-Abfragevorgängen (C#).

Starten eines Abfrageausdrucks

Ein Abfrageausdruck muss mit einer from-Klausel beginnen. Er legt eine Datenquelle zusammen mit einer Bereichsvariablen fest. Die Bereichsvariable stellt jedes darauf folgende Element in der Quellsequenz dar, während die Quellsequenz traversiert. Die Bereichsvariable weist je nach Typ der Elemente in der Datenquelle eine sehr starke Typisierung auf. Da countries ein Array von Country-Objekten ist, ist im folgenden Beispiel auch die Bereichsvariable als Country typisiert. Da die Bereichsvariable eine sehr starke Typisierung aufweist, 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 Gültigkeitsbereich, bis die Abfrage mit einem Semikolon oder einer Fortsetzungsklausel beendet wird.

Ein Abfrageausdruck enthält möglicherweise mehrere from-Klauseln. Verwenden Sie from-Klauseln, wenn jedes Element in der Quellsequenz selbst eine Auflistung ist oder eine Auflistung enthält. Nehmen Sie beispielsweise an, dass Sie eine Auflistung von Country-Objekten haben, von denen wiederum jedes eine Auflistung von City-Objekten mit dem Namen Cities enthält. Um die City-Objekte in jedem Country abzufragen, verwenden Sie wie hier gezeigt zwei from-Klauseln:

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 (C#-Referenz).

Beenden eines Abfrageausdrucks

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

group-Klausel

Verwenden Sie die group-Klausel, um eine Sequenz von Gruppen zu erzeugen, die von einem von Ihnen angegebenen Schlüssel organisiert wird. Der Schlüssel kann jeder Datentyp sein. Die folgende Abfrage erstellt beispielsweise eine Sequenz von Gruppen, die ein oder mehr Country-Objekte enthalten 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 (C#-Referenz).

select-Klausel

Verwenden Sie die select-Klausel, um alle anderen Typen von Sequenzen zu erzeugen. Eine einfache select-Klausel erzeugt nur eine Sequenz des gleichen Typs von Objekten 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 Umwandeln von Quelldaten in Sequenzen neuer Typen verwendet werden. Diese Umwandlung wird auch als Projektion bezeichnet. Die select-Klausel projiziert im folgenden Beispiel eine Sequenz anonymer Typen, die nur eine Teilmenge der Felder im ursprünglichen Element enthält. 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, mit denen eine select-Klausel zum Umwandeln der Quelldaten verwendet werden kann, finden Sie unter select-Klausel (C#-Referenz).

Fortsetzungen mit "into"

Mit dem into-Schlüsselwort in einer select-Klausel oder einer group-Klausel können Sie einen temporären Bezeichner erstellen, der eine Abfrage speichert. Führen Sie dies aus, wenn Sie zusätzliche Abfrageoperationen für eine Abfrage nach einer Gruppierungs- oder Auswahloperation ausführen müssen. Im folgenden Beispiel werden countries gemäß der Bevölkerung in Bereichen von 10 Millionen gruppiert. Nachdem diese Gruppen erstellt wurden, können Sie mit zusätzlichen Klauseln einige Gruppen filtern und sie dann in aufsteigender Reihenfolge sortieren. Um diese zusätzlichen Operationen auszuführen, ist die durch countryGroup dargestellte Fortsetzung erforderlich.

// percentileQuery is an IEnumerable<IGrouping<int, Country>> 
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10000000
    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 (C#-Referenz).

Filtern, sortieren und verknüpfen

Zwischen der from-Startklausel und der select-Endklausel bzw. group-Endklausel können alle anderen Klauseln (where, join, orderby, from, let) optional verwendet werden. Die optionalen Klauseln werden gar nicht oder mehrfach in einem Abfragetext verwendet.

where-Klausel

Verwenden Sie die where-Klausel, um Elemente aus den Quelldaten anhand eines oder mehrerer Prädikatsausdrücke herauszufiltern. Die where-Klausel im folgenden Beispiel verfügt über zwei Prädikate.

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

Weitere Informationen finden Sie unter where-Klausel (C#-Referenz).

orderby-Klausel

Verwenden Sie die orderby-Klausel, um die Ergebnisse entweder in aufsteigender oder absteigender Reihenfolge zu sortieren. Sie können auch sekundäre Sortierreihenfolgen angeben. Im folgenden Beispiel wird eine primäre Sortierung für die country-Objekte mithilfe der Area-Eigenschaft durchgeführt. Es wird dann eine sekundäre Sortierung mit der Population-Eigenschaft durchgeführt.

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

Das ascending-Schlüsselwort ist optional; wenn keine Reihenfolge angegeben wurde, handelt es sich um die Standardsortierreihenfolge. Weitere Informationen finden Sie unter orderby-Klausel (C#-Referenz).

join-Klausel

Verwenden Sie die join-Klausel, um Elemente aus einer Datenquelle Elementen aus einer anderen Datenquelle anhand eines Übereinstimmungsvergleichs zwischen angegebenen Schlüsseln in jedem Element zuzuweisen und/oder mit ihnen zu kombinieren. In LINQ werden Joinoperationen für Sequenzen von Objekten ausgeführt, deren Elemente unterschiedliche Typen sind. Nachdem Sie zwei Sequenzen verknüpft haben, müssen Sie eine select-Anweisung oder eine 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 zugeordneter Elemente zu einem neuen Typ für die Ausgabesequenz zu kombinieren. Im folgenden Beispiel werden prod-Objekte zugeordnet, deren Category-Eigenschaft einer der Kategorien im categories-Zeichenfolgenarray entspricht. Produkte, deren Category in categories zu keiner Zeichenfolge passt, werden herausgefiltert. Die select-Anweisung projiziert einen neuen Typ, dessen Eigenschaften aus cat und prod genommen werden.

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 einen Group Join durchführen, indem Sie die Ergebnisse der join-Operation in einer temporären Variable mit dem into-Schlüsselwort speichern. Weitere Informationen finden Sie unter join-Klausel (C#-Referenz).

let-Klausel

Verwenden Sie die let-Klausel, um das Ergebnis eines Ausdrucks, z. B. ein Methodenaufruf, in einer neuen Bereichsvariablen zu speichern. Im folgenden Beispiel speichert die firstName-Bereichsvariable das erste Element des Zeichenfolgenarrays, 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(new char[] { ' ' })[0]
    select firstName;

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

Weitere Informationen finden Sie unter let-Klausel (C#-Referenz).

Unterabfragen in einem Abfragenausdruck

Eine Abfrageklausel kann selbst einen Abfrageausdruck enthalten, der gelegentlich als Unterabfrage bezeichnet wird. Jede Unterabfrage beginnt mit ihrer eigenen from-Klausel, die nicht unbedingt auf die gleiche Datenquelle in der ersten from-Klausel verweist. Die folgende Abfrage zeigt beispielsweise einen Abfrageausdruck, der in der Auswahlanweisung zum Abrufen der Ergebnisse einer Gruppierungsoperation 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 Gewusst wie: Ausführen einer Unterabfrage für eine Gruppierungsoperation (C#-Programmierhandbuch).

Siehe auch

Konzepte

C#-Programmierhandbuch

LINQ-Abfrageausdrücke (C#-Programmierhandbuch)

Übersicht über Standardabfrageoperatoren

Weitere Ressourcen

LINQ (Language-Integrated Query, sprachintegrierte Abfrage)

Abfrageschlüsselwörter (C#-Referenz)