Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die meisten Abfragen in der einführenden LINQ-Dokumentation (Language Integrated Query) verwenden die LINQ-deklarative Abfragesyntax. Der C#-Compiler übersetzt die Abfragesyntax in Methodenaufrufe. Diese Methodenaufrufe implementieren die Standardabfrageoperatoren. Sie haben Namen wie Where, , Select, GroupByJoin, , Maxund Average. Sie rufen sie direkt mithilfe der Methodensyntax anstelle der Abfragesyntax auf.
Abfragesyntax und Methodensyntax sind semantisch identisch, aber Abfragesyntax ist häufig einfacher und leichter zu lesen. Sie müssen einige Abfragen als Methodenaufrufe ausdrücken. Sie müssen z. B. einen Methodenaufruf verwenden, um eine Abfrage auszudrücken, die die Anzahl der Elemente abruft, die einer angegebenen Bedingung entsprechen. Sie müssen einen Methodenaufruf auch für eine Abfrage verwenden, die das Element abruft, das den Maximalwert in der Quellsequenz hat. In der Referenzdokumentation für die Standardabfrageoperatoren im System.Linq-Namespace wird im Allgemeinen die Methodensyntax verwendet. Machen Sie sich mit der Verwendung der Methodensyntax in Abfragen und in Abfrageausdrücken vertraut.
Erweiterungsmethoden von Standardabfrageoperatoren
Im folgenden Beispiel wird ein einfacher Abfrageausdruck und die semantisch äquivalente Abfrage gezeigt, die als methodenbasierte Abfrage geschrieben ist.
int[] numbers = [ 5, 10, 8, 3, 6, 12 ];
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers
.Where(num => num % 2 == 0)
.OrderBy(n => n);
foreach (int i in numQuery1)
{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}
Die Ausgabe der beiden Beispiele ist identisch. Der Typ der Abfragevariable ist in beiden Formularen identisch: IEnumerable<T>.
Beachten Sie, dass die where-Klausel auf der rechten Seite des Ausdrucks jetzt als Instanzmethode des numbers-Objekts ausgedrückt wird. Sie ist vom Typ IEnumerable<int>. Wenn Sie mit der generischen IEnumerable<T>-Schnittstelle vertraut sind, wissen Sie, dass sie über keine Where-Methode verfügt. Wenn Sie jedoch die IntelliSense-Vervollständigungsliste in der Visual Studio IDE aufrufen, sehen Sie nicht nur eine Where-Methode, sondern viele andere Methoden, z. B. Select, SelectMany, Join und Orderby. Diese Methoden werden als Standardabfrageoperatoren implementiert.
Auch wenn es so aussieht, als ob IEnumerable<T> weitere Methoden enthält, ist dies jedoch nicht der Fall. Die Standardabfrageoperatoren werden als Erweiterungsmethoden implementiert. Erweiterungsmitglieder "erweitern" einen vorhandenen Typ; sie können so aufgerufen werden, als wären sie Mitglieder des Typs. Die Standardabfrageoperatoren erweitern IEnumerable<T>und deshalb können Sie schreiben numbers.Where(...). Mit using-Direktiven binden Sie Erweiterungen in den Gültigkeitsbereich ein, bevor Sie sie aufrufen.
Weitere Informationen zu Erweiterungsmitgliedern finden Sie unter Erweiterungsmitglieder. Weitere Informationen über Standardabfrageoperatoren finden Sie unter Standard Query Operators Overview (C#) (Übersicht über Standardabfrageoperatoren (C#)). Einige LINQ-Anbieter, z. B. Entity Framework und LINQ to XML, implementieren ihre eigenen Standardabfrageoperatoren und Erweiterungsmitglieder für andere Typen außer IEnumerable<T>.
Lambdaausdrücke
Im vorherigen Beispiel wird der bedingte Ausdruck num % 2 == 0 als Inlineargument an die Enumerable.Where-Methode übergeben: Where(num => num % 2 == 0).. Dieser Inlineausdruck wird als Lambdaausdruck bezeichnet. Es ist eine bequeme Möglichkeit zum Schreiben von Code, der sonst auf umständlichere Weise geschrieben werden müsste.
num auf der linken Seite des Operators ist die Eingabevariable, die num im Eingabeausdruck entspricht. Der Compiler kann den Typ von num ableiten, da er weiß, dass es sich bei numbers um einen generischen IEnumerable<T>-Typ handelt. Der Text des Lambdaausdrucks entspricht dem Ausdruck in der Abfragesyntax oder in einem anderen Ausdruck oder einer anderen Anweisung in C#. Er kann Methodenaufrufe und andere komplexe Logik enthalten. Der Rückgabewert ist das Ergebnis des Ausdrucks. Sie können nur bestimmte Abfragen in der Methodensyntax ausdrücken, und einige dieser Abfragen erfordern Lambda-Ausdrücke. Lambdaausdrücke sind ein leistungsfähiges und flexibles Tool in Ihrer LINQ-Toolbox.
Zusammensetzbarkeit von Abfragen
Im vorherigen Codebeispiel wird die Enumerable.OrderBy-Methode mit dem Punktoperator im Aufruf von Where aufgerufen.
Where erzeugt eine gefilterte Sequenz und Orderby sortiert dann die von Where generierte Sequenz. Da Abfragen IEnumerable zurückgeben, erstellen Sie sie in der Methodensyntax durch Verkettung von Methodenaufrufen miteinander. Der Compiler nimmt diese Komposition vor, wenn Sie Abfragen mithilfe der Abfragesyntax schreiben. Da eine Abfragevariable die Ergebnisse der Abfrage nicht speichert, können Sie sie jederzeit ändern oder als Basis für eine neue Abfrage verwenden, sogar, wenn sie bereits ausgeführt wurde.
Die folgenden Beispiele veranschaulichen einige grundlegende LINQ-Abfragen mithilfe der zuvor aufgeführten Ansätze.
Hinweis
Diese Abfragen werden in Speichersammlungen ausgeführt; Die Syntax ist jedoch identisch mit der Syntax, die in LINQ to Entities und LINQ to XML verwendet wird.
Beispiel : Abfragesyntax
Schreiben Sie die meisten Abfragen mit Abfragesyntax , um Abfrageausdrücke zu erstellen. Im folgenden Beispiel werden drei Abfrageausdrücke gezeigt. Der erste Abfrageausdruck veranschaulicht, wie man Ergebnisse durch Anwenden von Bedingungen mit einer where-Klausel filtern und einschränken kann. Er gibt alle Elemente in der Quellsequenz zurück, deren Wert größer als 7 oder kleiner als 3 ist. Der zweite Ausdruck veranschaulicht, wie man die zurückgegebenen Ergebnisse sortiert. Der dritte Ausdruck veranschaulicht, wie man Ergebnisse nach einem Schlüssel gruppiert. Diese Abfrage gibt basierend auf dem ersten Buchstaben des Worts zwei Gruppen zurück.
List<int> numbers = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ];
// The query variables can also be implicitly typed by using var
// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num is < 3 or > 7
select num;
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num is < 3 or > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
Der Typ der Abfragen ist IEnumerable<T>. Alle diese Abfragen können unter Verwendung von var wie im folgenden Beispiel geschrieben werden:
var query = from num in numbers...
In allen vorherigen Beispielen werden die Abfragen nicht ausgeführt, bis Sie die Iteration über die Abfragevariable in einer foreach- oder einer anderen Anweisung durchlaufen haben.
Beispiel : Methodensyntax
Sie müssen einige Abfragevorgänge als Methodenaufruf ausdrücken. Die häufigsten derartigen Methoden sind die, die einzelne numerische Werte zurückgeben, wie z. B. Sum, Max, Min, Average usw. Rufen Sie diese Methoden zuletzt in einer Abfrage auf, da sie einen einzelnen Wert zurückgeben und nicht als Quelle für weitere Abfragevorgänge dienen können. Im folgenden Beispiel wird ein Methodenaufruf in einem Abfrageausdruck dargestellt:
List<int> numbers1 = [ 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 ];
List<int> numbers2 = [ 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 ];
// Query #4.
double average = numbers1.Average();
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
Wenn die Methode System.Action oder System.Func<TResult> Parameter hat, geben Sie diese Argumente in Form eines Lambda-Ausdrucks an, wie im folgenden Beispiel gezeigt:
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
In den vorherigen Abfragen wird nur Abfrage #4 sofort ausgeführt, da sie einen einzelnen Wert und keine generische IEnumerable<T> Auflistung zurückgibt. Die Methode selbst verwendet foreach oder ähnlichen Code, um den Wert zu berechnen.
Sie können jede der vorherigen Abfragen schreiben, indem Sie implizite Typisierung mit var verwenden, wie im Folgenden gezeigt:
// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);
Beispiel : Gemischte Abfrage- und Methodensyntax
In diesem Beispiel wird veranschaulicht, wie Sie die Methodensyntax auf die Ergebnisse einer Abfrageklausel anwenden können. Schließen Sie den Abfrageausdruck in Klammern ein, und wenden Sie dann den Punktoperator an, und rufen Sie die Methode auf. Im folgenden Beispiel wird von der siebten Abfrage die Anzahl der Zahlen zurückgegeben, deren Wert zwischen 3 und 7 liegt.
// Query #7.
// Using a query expression with method syntax
var numCount1 = (
from num in numbers1
where num is > 3 and < 7
select num
).Count();
// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num is > 3 and < 7
select num;
var numCount2 = numbersQuery.Count();
Da Abfrage Nr. 7 einen einzelnen Wert und keine Auflistung zurückgibt, wird die Abfrage sofort ausgeführt.
Sie können die vorherige Abfrage mit impliziter Eingabe var wie folgt schreiben:
var numCount = (from num in numbers...
Sie können sie wie folgt in methodensyntax schreiben:
var numCount = numbers.Count(n => n is > 3 and < 7);
Sie können sie mit expliziter Eingabe wie folgt schreiben:
int numCount = numbers.Count(n => n is > 3 and < 7);
Dynamisches Festlegen von Prädikatfiltern zur Laufzeit
In einigen Fällen wissen Sie bis zur Laufzeit nicht, wie viele Prädikate Sie für die Quellelemente in die where-Klausel übernehmen müssen. Eine Möglichkeit, mehrere Prädikatfilter dynamisch festzulegen, ist die Verwendung der Methode Contains, wie im folgenden Beispiel gezeigt wird. Wenn die Abfrage ausgeführt wird, liefert sie je nach dem Wert von id unterschiedliche Ergebnisse.
int[] ids = [ 111, 114, 112 ];
var queryNames = from student in students
where ids.Contains(student.ID)
select new
{
student.LastName,
student.ID
};
foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
/* Output:
Garcia: 114
O'Donnell: 112
Omelchenko: 111
*/
// Change the ids.
ids = [ 122, 117, 120, 115 ];
// The query will now return different results
foreach (var name in queryNames)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
/* Output:
Adams: 120
Feng: 117
Garcia: 115
Tucker: 122
*/
Hinweis
In diesem Beispiel werden die folgenden Datenquellen und Daten 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)])
];
Sie können mithilfe von Ablaufsteuerungsanweisungen wie if... else oder switch eine der vorgegebenen alternativen Abfragen auswählen. Im folgenden Beispiel verwendet studentQuery eine andere where-Klausel, wenn der Laufzeitwert von oddYear entweder true oder false ist.
void FilterByYearType(bool oddYear)
{
IEnumerable<Student> studentQuery = oddYear
? (from student in students
where student.Year is GradeLevel.FirstYear or GradeLevel.ThirdYear
select student)
: (from student in students
where student.Year is GradeLevel.SecondYear or GradeLevel.FourthYear
select student);
var descr = oddYear ? "odd" : "even";
Console.WriteLine($"The following students are at an {descr} year level:");
foreach (Student name in studentQuery)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}
FilterByYearType(true);
/* Output:
The following students are at an odd year level:
Fakhouri: 116
Feng: 117
Garcia: 115
Mortensen: 113
Tucker: 119
Tucker: 122
*/
FilterByYearType(false);
/* Output:
The following students are at an even year level:
Adams: 120
Garcia: 114
Garcia: 118
O'Donnell: 112
Omelchenko: 111
Zabokritski: 121
*/
Behandeln von NULL-Werten in Abfrageausdrücken
Dieses Beispiel zeigt, wie mögliche NULL-Werte in Quellauflistungen behandelt werden. Eine Objektauflistung wie z. B. IEnumerable<T> kann Elemente enthalten, deren Wert NULL ist. Wenn eine Quellauflistung null ist oder ein Element enthält, dessen Wert null ist, und die Abfrage keine null-Werte verarbeitet, wird eine NullReferenceException ausgelöst, wenn Sie die Abfrage ausführen.
Im folgenden Beispiel werden diese Typen und statische Datenarrays verwendet:
record Product(string Name, int CategoryID);
record Category(string Name, int ID);
static Category?[] categories =
[
new ("brass", 1),
null,
new ("winds", 2),
default,
new ("percussion", 3)
];
static Product?[] products =
[
new Product("Trumpet", 1),
new Product("Trombone", 1),
new Product("French Horn", 1),
null,
new Product("Clarinet", 2),
new Product("Flute", 2),
null,
new Product("Cymbal", 3),
new Product("Drum", 3)
];
Sie können defensiv codieren, um eine Nullverweisausnahme wie im folgenden Beispiel dargestellt zu vermeiden:
var query1 = from c in categories
where c != null
join p in products on c.ID equals p?.CategoryID
select new
{
Category = c.Name,
Name = p.Name
};
Im vorherigen Beispiel filtert die where-Klausel alle NULL-Elemente in der Reihenfolge der Kategorien heraus. Diese Technik ist unabhängig von der NULL-Überprüfung in der join-Klausel. Der bedingte Ausdruck mit NULL in diesem Beispiel funktioniert, da Products.CategoryID vom Typ int? ist, was eine Abkürzung für Nullable<int> ist.
Wenn in einer join-Klausel nur einer der Vergleichsschlüssel ein Nullable-Werttyp ist, können Sie den anderen Schlüssel im Abfrageausdruck in einen Nullable-Werttyp umwandeln. Im folgenden Beispiel wird angenommen, dass EmployeeID eine Spalte mit Werten vom Typ int? ist:
var query =
from o in db.Orders
join e in db.Employees
on o.EmployeeID equals (int?)e.EmployeeID
select new { o.OrderID, e.FirstName };
In jedem der Beispiele wird das equals Abfragenschlüsselwort verwendet. Sie können auch einen Musterabgleich verwenden, der Muster für is null und is not null enthält. Diese Muster werden in LINQ-Abfragen nicht empfohlen, da Abfrageanbieter die neue C#-Syntax möglicherweise nicht richtig interpretieren. Ein Abfrageanbieter ist eine Bibliothek, die C#-Abfrageausdrücke in ein natives Datenformat übersetzt, z. B. Entity Framework Core. Abfrageanbieter implementieren die System.Linq.IQueryProvider -Schnittstelle, um Datenquellen zu erstellen, die die -Schnittstelle System.Linq.IQueryable<T> implementieren.
Behandeln von Ausnahmen in Abfrageausdrücken
Sie können eine beliebige Methode im Kontext eines Abfrageausdrucks aufrufen. Rufen Sie keine Methoden in einem Abfrageausdruck auf, die Nebeneffekte wie die Änderung des Inhalts der Datenquelle oder das Auslösen einer Ausnahme erzeugen können. In diesem Beispiel wird veranschaulicht, wie Sie es beim Aufrufen von Methoden in Abfrageausdrücken vermeiden, Ausnahmen auszulösen, ohne gegen die allgemeinen .NET-Richtlinien für die Behandlung von Ausnahmen zu verstoßen. Diese Richtlinien geben an, dass es akzeptabel ist, eine bestimmte Ausnahme zu erfassen, wenn Sie verstehen, warum sie in einem bestimmten Kontext ausgelöst wird. Weitere Informationen finden Sie unter Best Practices für Ausnahmen.
Im letzten Beispiel wird der Umgang mit diesen Fällen veranschaulicht, wenn Sie während der Ausführung einer Abfrage eine Ausnahme auslösen müssen.
Im folgenden Beispiel wird veranschaulicht, wie Sie den Ausnahmebehandlungscode aus einem Abfrageausdruck verschieben. Sie können diese Art und Weise nur umgestalten, wenn die Methode nicht von lokalen Variablen für die Abfrage abhängt. Die Handhabung von Ausnahmen außerhalb des Abfrageausdrucks ist einfacher.
// A data source that is very likely to throw an exception!
IEnumerable<int> GetData() => throw new InvalidOperationException();
// DO THIS with a datasource that might
// throw an exception.
IEnumerable<int>? dataSource = null;
try
{
dataSource = GetData();
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation");
}
if (dataSource is not null)
{
// If we get here, it is safe to proceed.
var query = from i in dataSource
select i * i;
foreach (var i in query)
{
Console.WriteLine(i.ToString());
}
}
catch (InvalidOperationException) Behandeln Sie im Block im vorherigen Beispiel die Ausnahme (oder behandeln Sie sie nicht) so, wie sie für Ihre Anwendung geeignet ist.
Wenn von innerhalb einer Abfrage eine Ausnahme ausgelöst wird, kann es in einigen Fällen am besten sein, die Abfrage sofort zu beenden. Im folgenden Beispiel wird der Umgang mit Ausnahmen veranschaulicht, die innerhalb eines Abfragetexts ausgelöst werden können. Angenommen dass SomeMethodThatMightThrow zu einer Ausnahme führen kann, derentwegen die Ausführung der Abfrage beenden werden muss.
Der try-Block enthält die foreach-Schleife und nicht die Abfrage selbst. Die foreach-Schleife ist der Punkt, an dem die Abfrage tatsächlich ausgeführt wird. Die Runtime-Ausnahmen werden ausgelöst, wenn die Abfrage ausgeführt wird. Behandeln Sie sie daher in der foreach Schleife.
// Not very useful as a general purpose method.
string SomeMethodThatMightThrow(string s) =>
s[4] == 'C' ?
throw new InvalidOperationException() :
$"""C:\newFolder\{s}""";
// Data source.
string[] files = ["fileA.txt", "fileB.txt", "fileC.txt"];
// Demonstration query that throws.
var exceptionDemoQuery = from file in files
let n = SomeMethodThatMightThrow(file)
select n;
try
{
foreach (var item in exceptionDemoQuery)
{
Console.WriteLine($"Processing {item}");
}
}
catch (InvalidOperationException e)
{
Console.WriteLine(e.Message);
}
/* Output:
Processing C:\newFolder\fileA.txt
Processing C:\newFolder\fileB.txt
Operation is not valid due to the current state of the object.
*/
Erfassen Sie jede Ausnahme, die Sie auslösen möchten, und führen Sie alle erforderlichen Bereinigungen in einem finally Block durch.