Grundlagen zu Abfrageausdrücken (C#-Programmierhandbuch)
Aktualisiert: November 2007
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<T>-Auflistung oder IQueryable<T>-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 anstatt die 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: 90 82 93 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();
IEnumerable<City> largeCityList =
(from country in countries
from city in country.Cities
where city.Population > 10000
select city)
.ToList();
// or split the expression
IEnumerable<City> largeCityList2 =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
List<City> largeCities = largeCityList2.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 (LINQ).
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 unter Typbeziehungen in Abfrageoperationen (LINQ).
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 Zeichenfolgenwert 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 > 500000, 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 Verknüpfungsoperationen 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 eine Gruppenverknüpfung 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 s-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 hierzu 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
LINQ-Abfrageausdrücke (C#-Programmierhandbuch)
Referenz
Allgemeines LINQ-Programmierhandbuch