Grundlagen zu Abfrageausdrücken
In diesem Artikel werden die grundlegenden Konzepte für Abfrageausdrücke in C# vorgestellt.
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 XML-Elemente hierarchisch in einer Baumstruktur organisiert sind). Eine Auflistung im Arbeitsspeicher enthält eine Sequenz von Objekten.
Aus Sicht einer Anwendung sind der spezifische Typ und die Struktur der ursprünglichen Datenquelle nicht wichtig. Die Anwendung sieht die Quelldaten immer als eine IEnumerable<T>- oder IQueryable<T>-Auflistung an. In LINQ to XML werden die Quelldaten z. B. als 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
istint[]
):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 von XML-Daten vor dem Generieren der endgültigen Ergebnissequenz verwenden. Im folgenden Beispiel wird eine Projektion von
int
instring
veranschaulicht. Beachten Sie den neuen Typ vonhighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {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:var 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 Enumerable.Count-Methode. Sie können auch eine neue Variable verwenden, um das konkrete Ergebnis zu speichern.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var 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. Sie 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 weitere 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 aufzählbarer Typ, der eine Sequenz von Elementen erzeugt, wenn er in einer foreach
-Anweisung oder einem direkten Aufruf der IEnumerator.MoveNext()-Methode durchlaufen wird.
Hinweis
In den Beispielen in diesem Artikel werden die folgende Datenquelle und Beispieldaten verwendet.
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
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.
// 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 (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
// Output: 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:
City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 100000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Population = 120000 }
// City { Population = 112000 }
// City { Population = 150340 }
// 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:
var highestScore = (
from score in scores
select score
).Max();
// or split the expression
IEnumerable<int> scoreQuery =
from score in scores
select score;
var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var 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;
var largeCitiesList2 = largeCitiesQuery.ToList();
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 Artikel gezeigt wurde, kann beispielsweise auch durch implizierte Typisierung ausgedrückt werden:
var queryCities =
from city in cities
where city.Population > 100000
select city;
Im vorherigen Beispiel ist die Verwendung von „var“ optional. queryCities
ist mit impliziter oder expliziter Typisierung IEnumerable<City>
.
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 weitere 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 enthalten und deren Schlüssel vom Typ char
ist und als Wert den ersten Buchstaben von Ländern enthält.
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 projiziert die select
-Klausel eine Sequenz anonymer Typen, die nur eine Teilmenge der Felder im ursprünglichen Element enthält. Die neuen Objekte werden mit einem Objektinitialisierer initialisiert.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
In diesem Beispiel ist var
erforderlich, da die Abfrage einen anonymen Typ erzeugt.
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. Verwenden Sie die into
-Klausel, wenn Sie zusätzliche Abfragevorgänge nach einem Gruppierungs- oder Auswahlvorgang für 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 weitere 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 is < 200000 and > 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 Sequenzen 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
-Element 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 (var s in queryFirstNames)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
Weitere Informationen finden Sie unter let-Klausel.
Unterabfragen in einem Abfrageausdruck
Eine Abfrageklausel kann selbst einen Abfrageausdruck enthalten, der 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 verweist. 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.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};
Weitere Informationen finden Sie unter Ausführen einer Unterabfrage für einen Gruppierungsvorgang.