Freigeben über


Übersicht über Standardabfrageoperatoren

Die Standardabfrageoperatoren sind die Schlüsselwörter und Methoden, die das LINQ-Muster bilden. Die C#-Sprache definiert LINQ-Abfragestichwörter , die Sie für den am häufigsten verwendeten Abfrageausdruck verwenden. Der Compiler übersetzt Ausdrücke mithilfe dieser Schlüsselwörter in die entsprechenden Methodenaufrufe. Die beiden Formen sind synonym. Andere Methoden, die Teil des System.Linq Namespaces sind, verfügen nicht über entsprechende Abfragestichwörter. In diesen Fällen müssen Sie die Methodensyntax verwenden. In diesem Abschnitt werden alle Schlüsselwörter des Abfrageoperators behandelt. Die Laufzeit- und anderen NuGet-Pakete fügen mit jeder veröffentlichten Version mehr Methoden hinzu, die für LINQ-Abfragen entwickelt wurden. Die am häufigsten verwendeten Methoden, einschließlich der Methoden, die Abfragestichwortentsprechungen aufweisen, werden in diesem Abschnitt behandelt. Eine vollständige Liste der abfragemethoden, die von .NET Runtime unterstützt werden, finden Sie in der System.Linq.Enumerable API-Dokumentation. Zusätzlich zu den hier behandelten Methoden enthält diese Klasse Methoden zum Verketten von Datenquellen, zum Berechnen eines einzelnen Werts aus einer Datenquelle, z. B. einer Summe, eines Mittelwerts oder eines anderen Werts.

Von Bedeutung

In diesen Beispielen wird eine System.Collections.Generic.IEnumerable<T>-Datenquelle verwendet. Datenquellen, die auf System.Linq.IQueryProvider basieren, verwenden System.Linq.IQueryable<T>-Datenquellen und Ausdrucksbaumstrukturen. Ausdrucksbaumstrukturen haben Einschränkungen für die zulässige C#-Syntax. Darüber hinaus kann jede IQueryProvider-Datenquelle, z. B. EF Core, weitere Einschränkungen erzwingen. Konsultieren Sie die Dokumentation für Ihre Datenquelle.

Die meisten dieser Methoden werden auf Sequenzen ausgeführt, wobei es sich bei einer Sequenz um ein Objekt handelt, dessen Typ die IEnumerable<T> Schnittstelle oder schnittstelle IQueryable<T> implementiert. Die Standardabfrageoperatoren bieten Abfragefunktionen, einschließlich Filterung, Projektion, Aggregation, Sortierung und mehr. Die Methoden, aus denen die einzelnen Gruppen bestehen, sind statische Mitglieder der Enumerable bzw. Queryable Klassen. Sie werden als Erweiterungsmethoden des Typs definiert, auf dem sie arbeiten.

Die Unterscheidung zwischen IEnumerable<T> und IQueryable<T> Sequenz bestimmt, wie die Abfrage zur Laufzeit ausgeführt wird.

Für IEnumerable<T> erfasst das zurückgegebene aufzählbare Objekt die Argumente, die an die Methode übergeben wurden. Wenn dieses Objekt aufgezählt wird, wird die Logik des Abfrageoperators verwendet, und die Abfrageergebnisse werden zurückgegeben.

Für IQueryable<T> wird die Abfrage in einen expression tree übersetzt. Der Ausdrucksbaum kann in eine systeminterne Abfrage übersetzt werden, wenn die Datenquelle die Abfrage optimieren kann. Bibliotheken wie Entity Framework übersetzen LINQ-Abfragen in systemeigene SQL-Abfragen, die in der Datenbank ausgeführt werden.

Im folgenden Codebeispiel wird veranschaulicht, wie die Standardabfrageoperatoren verwendet werden können, um Informationen zu einer Sequenz abzurufen.

string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');

// Using query expression syntax.
var query = from word in words
            group word.ToUpper() by word.Length into gr
            orderby gr.Key
            select new { Length = gr.Key, Words = gr };

// Using method-based query syntax.
var query2 = words.
    GroupBy(w => w.Length, w => w.ToUpper()).
    Select(g => new { Length = g.Key, Words = g }).
    OrderBy(o => o.Length);

foreach (var obj in query)
{
    Console.WriteLine($"Words of length {obj.Length}:");
    foreach (string word in obj.Words)
        Console.WriteLine(word);
}

// This code example produces the following output:
//
// Words of length 3:
// THE
// FOX
// THE
// DOG
// Words of length 4:
// OVER
// LAZY
// Words of length 5:
// QUICK
// BROWN
// JUMPS

Wenn möglich, verwenden die Abfragen in diesem Abschnitt eine Abfolge von Wörtern oder Zahlen als Eingabequelle. Bei Abfragen, bei denen kompliziertere Beziehungen zwischen Objekten verwendet werden, werden die folgenden Quellen verwendet, die eine Schule modellieren:

public enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

public class Student
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required int ID { get; init; }

    public required GradeLevel Year { get; init; }
    public required List<int> Scores { get; init; }

    public required int DepartmentID { get; init; }
}

public class Teacher
{
    public required string First { get; init; }
    public required string Last { get; init; }
    public required int ID { get; init; }
    public required string City { get; init; }
}

public class Department
{
    public required string Name { get; init; }
    public int ID { get; init; }

    public required int TeacherID { get; init; }
}

Allen Student sind eine Klassenstufe, ein primärer Fachbereich und mehrere Bewertungen zugeordnet. Teacher verfügen auch über eine City-Eigenschaft, die den Campus identifiziert, auf dem die Lehrkraft unterrichtet. Eine Department hat einen Namen und einen Verweis auf eine Teacher, die den Fachbereich leitet.

Sie finden das Dataset im Quell-Repository.

Abfrageoperatorentypen

Die Standardabfrageoperatoren unterscheiden sich im Zeitpunkt ihrer Ausführung, je nachdem, ob sie einen einzelnen Wert oder eine Folge von Werten zurückgeben. Diese Methoden, die einen Singletonwert (z. B. Average und Sum) zurückgeben, werden sofort ausgeführt. Methoden, die eine Sequenz zurückgeben, verzögern die Abfrageausführung und geben ein aufzählbares Objekt zurück. Sie können die Ausgabesequenz einer Abfrage als Eingabesequenz für eine andere Abfrage verwenden. Aufrufe von Abfragemethoden können in einer Abfrage verkettet werden, wodurch Abfragen beliebig komplex werden können.

Abfrageoperatoren

In einer LINQ-Abfrage besteht der erste Schritt darin, die Datenquelle anzugeben. In einer LINQ-Abfrage kommt die from Klausel zuerst, um die Datenquelle (students) und die Bereichsvariable (student) einzuführen.

//queryAllStudents is an IEnumerable<Student>
var queryAllStudents = from student in students
                        select student;

Die Bereichsvariable ist wie die Iterationsvariable in einer foreach Schleife, mit der Ausnahme, dass keine tatsächliche Iteration in einem Abfrageausdruck auftritt. Wenn die Abfrage ausgeführt wird, dient die Bereichsvariable als Verweis auf jedes aufeinander folgende Element in students. Da der Compiler den Typ von student ableiten kann, müssen Sie ihn nicht explizit angeben. Sie können weitere Bereichsvariablen in einer let Klausel einführen. Weitere Informationen finden Sie unter let-Klausel.

Hinweis

Bei nicht generischen Datenquellen wie ArrayListz. B. muss die Bereichsvariable explizit eingegeben werden. Weitere Informationen finden Sie unter How to query an ArrayList with LINQ (C#) und from clause.

Nachdem Sie eine Datenquelle erhalten haben, können Sie eine beliebige Anzahl von Vorgängen für diese Datenquelle ausführen:

Abfrageausdrucks-Syntax-Tabelle

In der folgenden Tabelle sind die Standardabfrageoperatoren aufgeführt, die über entsprechende Abfrageausdrucksklauseln verfügen.

Methode Syntax des C#-Abfrageausdrucks
Cast Verwenden Sie eine explizit eingegebene Bereichsvariable:

from int i in numbers

(Weitere Informationen finden Sie unter from-Klausel.)
GroupBy group … by

-oder-

group … by … into …

(Weitere Informationen finden Sie unter "group clause".)
GroupJoin<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,IEnumerable<TInner>, TResult>) join … in … on … equals … into …

(Weitere Informationen finden Sie unter join-Klausel.)
Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,TInner,TResult>) join … in … on … equals …

(Weitere Informationen finden Sie unter join-Klausel.)
OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby

(Weitere Informationen finden Sie unter orderby-Klausel.)
OrderByDescending<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby … descending

(Weitere Informationen finden Sie unter orderby-Klausel.)
Select select

(Weitere Informationen finden Sie unter Select-Klausel.)
SelectMany Mehrere from Klauseln.

(Weitere Informationen finden Sie unter from-Klausel.)
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, …

(Weitere Informationen finden Sie unter orderby-Klausel.)
ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, … descending

(Weitere Informationen finden Sie unter orderby-Klausel.)
Where where

(Weitere Informationen finden Sie unter where clause.)

Datentransformationen mit LINQ

Bei der sprachintegrierten Abfrage (LINQ) geht es nicht nur um das Abrufen von Daten. Es ist auch ein leistungsfähiges Tool zum Transformieren von Daten. Mithilfe einer LINQ-Abfrage können Sie eine Quellsequenz als Eingabe verwenden und auf vielfältige Weise ändern, um eine neue Ausgabesequenz zu erstellen. Sie können die Sequenz selbst ändern, ohne die Elemente selbst durch Sortieren und Gruppieren zu ändern. Vielleicht ist aber das leistungsstärkste Feature von LINQ-Abfragen die Möglichkeit, neue Typen zu erstellen. Die Auswahlklausel erstellt ein Ausgabeelement aus einem Eingabeelement. Sie verwenden es, um ein Eingabeelement in ein Ausgabeelement zu transformieren:

  • Führen Sie mehrere Eingabesequenzen in einer einzelnen Ausgabesequenz mit einem neuen Typ zusammen.
  • Erstellen Sie Ausgabesequenzen, deren Elemente aus nur einer oder mehreren Eigenschaften jedes Elements in der Quellsequenz bestehen.
  • Erstellen Sie Ausgabesequenzen, deren Elemente aus den Ergebnissen von Vorgängen bestehen, die für die Quelldaten ausgeführt werden.
  • Erstellen Sie Ausgabesequenzen in einem anderen Format. Sie können z. B. Daten aus SQL-Zeilen oder Textdateien in XML transformieren.

Diese Transformationen können auf unterschiedliche Weise in derselben Abfrage kombiniert werden. Darüber hinaus kann die Ausgabesequenz einer Abfrage als Eingabesequenz für eine neue Abfrage verwendet werden. Im folgenden Beispiel werden Objekte in einer Speicherdatenstruktur in XML-Elemente umgewandelt.


// Create the query.
var studentsToXML = new XElement("Root",
    from student in students
    let scores = string.Join(",", student.Scores)
    select new XElement("student",
                new XElement("First", student.FirstName),
                new XElement("Last", student.LastName),
                new XElement("Scores", scores)
            ) // end "student"
        ); // end "Root"

// Execute the query.
Console.WriteLine(studentsToXML);

Der Code erzeugt die folgende XML-Ausgabe:

<Root>
  <student>
    <First>Svetlana</First>
    <Last>Omelchenko</Last>
    <Scores>97,90,73,54</Scores>
  </student>
  <student>
    <First>Claire</First>
    <Last>O'Donnell</Last>
    <Scores>56,78,95,95</Scores>
  </student>
  ...
  <student>
    <First>Max</First>
    <Last>Lindgren</Last>
    <Scores>86,88,96,63</Scores>
  </student>
  <student>
    <First>Arina</First>
    <Last>Ivanova</Last>
    <Scores>93,63,70,80</Scores>
  </student>
</Root>

Weitere Informationen finden Sie unter Creating XML Trees in C# (LINQ to XML).

Sie können die Ergebnisse einer Abfrage als Datenquelle für eine nachfolgende Abfrage verwenden. In diesem Beispiel wird gezeigt, wie die Ergebnisse eines Verknüpfungsvorgangs sortiert werden. Diese Abfrage erstellt eine Gruppenverknüpfung und sortiert die Gruppen anschließend anhand des Elements der Kategorie, das sich noch im Geltungsbereich befindet. Innerhalb des anonymen Typinitialisierers sortiert eine Unterabfrage alle übereinstimmenden Elemente aus der Produktsequenz.

var orderedQuery = from department in departments
                   join student in students on department.ID equals student.DepartmentID into studentGroup
                   orderby department.Name
                   select new
                   {
                       DepartmentName = department.Name,
                       Students = from student in studentGroup
                                  orderby student.LastName
                                    select student
                   };

foreach (var departmentList in orderedQuery)
{
    Console.WriteLine(departmentList.DepartmentName);
    foreach (var student in departmentList.Students)
    {
        Console.WriteLine($"  {student.LastName,-10} {student.FirstName,-10}");
    }
}
/* Output:
Chemistry
  Balzan     Josephine
  Fakhouri   Fadi
  Popov      Innocenty
  Seleznyova Sofiya
  Vella      Carmen
Economics
  Adams      Terry
  Adaobi     Izuchukwu
  Berggren   Jeanette
  Garcia     Cesar
  Ifeoma     Nwanneka
  Jamuike    Ifeanacho
  Larsson    Naima
  Svensson   Noel
  Ugomma     Ifunanya
Engineering
  Axelsson   Erik
  Berg       Veronika
  Engström   Nancy
  Hicks      Cassie
  Keever     Bruce
  Micallef   Nicholas
  Mortensen  Sven
  Nilsson    Erna
  Tucker     Michael
  Yermolayeva Anna
English
  Andersson  Sarah
  Feng       Hanying
  Ivanova    Arina
  Jakobsson  Jesper
  Jensen     Christiane
  Johansson  Mark
  Kolpakova  Nadezhda
  Omelchenko Svetlana
  Urquhart   Donald
Mathematics
  Frost      Gaby
  Garcia     Hugo
  Hedlund    Anna
  Kovaleva   Katerina
  Lindgren   Max
  Maslova    Evgeniya
  Olsson     Ruth
  Sammut     Maria
  Sazonova   Anastasiya
Physics
  Åkesson    Sami
  Edwards    Amy E.
  Falzon     John
  Garcia     Debra
  Hansson    Sanna
  Mattsson   Martina
  Richardson Don
  Zabokritski Eugene
*/

Die entsprechende Abfrage mit Methodensyntax ist im folgenden Code dargestellt:

var orderedQuery = departments
    .GroupJoin(students, department => department.ID, student => student.DepartmentID,
    (department, studentGroup) => new
    {
        DepartmentName = department.Name,
        Students = studentGroup.OrderBy(student => student.LastName)
    })
    .OrderBy(department => department.DepartmentName);


foreach (var departmentList in orderedQuery)
{
    Console.WriteLine(departmentList.DepartmentName);
    foreach (var student in departmentList.Students)
    {
        Console.WriteLine($"  {student.LastName,-10} {student.FirstName,-10}");
    }
}

Obwohl Sie eine orderby Klausel mit einer oder mehreren Quellsequenzen vor der Verknüpfung verwenden können, wird dies im Allgemeinen nicht empfohlen. Einige LINQ-Anbieter behalten diese Sortierung nach dem Beitritt möglicherweise nicht bei. Weitere Informationen finden Sie unter join-Klausel.

Siehe auch