Udostępnij za pośrednictwem


Standardowe operatory zapytań — omówienie

Standardowe operatory zapytań to słowa kluczowe i metody, które tworzą wzorzec LINQ. Język C# definiuje słów kluczowych zapytań LINQ używanych do najbardziej typowego wyrażenia zapytania. Kompilator tłumaczy wyrażenia przy użyciu tych słów kluczowych do równoważnych wywołań metody. Te dwie formy są synonimami. Inne metody, które są częścią przestrzeni nazw System.Linq, nie mają równoważnych słów kluczowych zapytania. W takich przypadkach należy używać składni metod. W tej sekcji omówiono wszystkie słowa kluczowe operatora zapytania. Środowisko uruchomieniowe i inne pakiety NuGet dodają więcej metod zaprojektowanych do pracy z zapytaniami LINQ w każdej wersji. Najbardziej typowe metody, w tym te, które mają odpowiedniki słów kluczowych zapytania, zostały omówione w tej sekcji. Pełną listę metod zapytań obsługiwanych przez środowisko uruchomieniowe platformy .NET można znaleźć w dokumentacji interfejsu API System.Linq.Enumerable. Oprócz metod omówionych tutaj ta klasa zawiera metody łączenia źródeł danych, obliczania pojedynczej wartości ze źródła danych, takich jak suma, średnia lub inna wartość.

Ważne

Te przykłady korzystają ze źródła danych System.Collections.Generic.IEnumerable<T>. Źródła danych oparte na System.Linq.IQueryProvider używają źródeł danych System.Linq.IQueryable<T> i drzew wyrażeń . Drzewa wyrażeń mają ograniczenia w dozwolonej składni języka C#. Ponadto każde źródło danych IQueryProvider, takie jak EF Core, może nakładać więcej ograniczeń. Zapoznaj się z dokumentacją źródła danych.

Większość z tych metod działa na sekwencjach, gdzie sekwencja jest obiektem, którego typ implementuje interfejs IEnumerable<T> lub interfejs IQueryable<T>. Standardowe operatory zapytań zapewniają możliwości zapytań, w tym filtrowanie, projekcję, agregację, sortowanie i nie tylko. Metody, które składają się na każdy zestaw, to statyczni członkowie klas Enumerable i Queryable. Są one definiowane jako metody rozszerzenia typu, na którym działają.

Rozróżnienie między sekwencjami IEnumerable<T> i IQueryable<T> określa sposób wykonywania zapytania w czasie wykonywania.

W przypadku IEnumerable<T>zwrócony obiekt wyliczalny przechwytuje argumenty przekazane do metody. Gdy ten obiekt zostanie wyliczony, zostanie zastosowana logika operatora zapytania, a wyniki zapytania zostaną zwrócone.

W przypadku IQueryable<T>zapytanie jest tłumaczone na drzewo wyrażeń . Drzewo wyrażeń można przetłumaczyć na zapytanie natywne, gdy źródło danych może zoptymalizować zapytanie. Biblioteki, takie jak Entity Framework, tłumaczą zapytania LINQ na natywne zapytania SQL, które są wykonywane w bazie danych.

Poniższy przykład kodu pokazuje, jak standardowe operatory zapytań mogą służyć do uzyskiwania informacji o sekwencji.

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

Jeśli to możliwe, zapytania w tej sekcji używają sekwencji słów lub liczb jako źródła danych wejściowych. W przypadku zapytań, w których są używane bardziej skomplikowane relacje między obiektami, używane są następujące źródła, które modelują szkołę:

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; }
}

Każda Student ma poziom klasy, dział podstawowy i serię wyników. Obiekt Teacher ma również właściwość City, która identyfikuje kampus, w którym nauczyciel prowadzi zajęcia. Department ma nazwę i odniesienie do Teacher, który służy jako szef działu.

Zestaw danych można znaleźć w repozytorium źródłowym .

Typy operatorów zapytań

Standardowe operatory zapytań różnią się czasem wykonywania w zależności od tego, czy zwracają pojedynczą wartość, czy sekwencję wartości. Metody zwracające pojedynczą wartość (na przykład Average i Sum) są wykonywane natychmiast. Metody, które zwracają sekwencję, odkładają wykonanie zapytania i zwracają obiekt wyliczalny. Możesz użyć sekwencji danych wyjściowych jednego zapytania jako sekwencji danych wejściowych do innego zapytania. Wywołania metod zapytań można połączyć w łańcuch w jednym zapytaniu, co umożliwia zapytania o dowolnym stopniu złożoności.

Operatory zapytań

W zapytaniu LINQ pierwszym krokiem jest określenie źródła danych. W zapytaniu LINQ klauzula from występuje jako pierwsza, aby wprowadzić źródło danych (students) i zmienną zakresu (student).

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

Zmienna zakresu jest podobna do zmiennej iteracji w pętli foreach, z tą różnicą, że w wyrażeniu zapytania nie ma rzeczywistej iteracji. Po wykonaniu zapytania zmienna zakresu służy jako odwołanie do każdego kolejnego elementu w students. Ponieważ kompilator może wywnioskować typ student, nie trzeba jawnie go określać. W klauzuli let można wprowadzić więcej zmiennych zakresu. Aby uzyskać więcej informacji, zobacz klauzulę let .

Uwaga

W przypadku źródeł danych innych niż ogólne, takich jak ArrayList, zmienna zakresu musi być jawnie wpisana. Aby uzyskać więcej informacji, zobacz How to query an ArrayList with LINQ (C#) and from clause.

Po uzyskaniu źródła danych można wykonać dowolną liczbę operacji na tym źródle danych:

Tabela składni wyrażeń zapytań

W poniższej tabeli wymieniono standardowe operatory zapytań, które mają równoważne klauzule wyrażenia zapytania.

Metoda Składnia wyrażeń zapytań języka C#
Cast Użyj jawnie typizowanej zmiennej zakresu:

from int i in numbers

(Aby uzyskać więcej informacji, zobacz z klauzuli.)
GroupBy group … by

— lub —

group … by … into …

(Aby uzyskać więcej informacji, zobacz klauzulę grupy .)
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 …

(Aby uzyskać więcej informacji, zobacz klauzulę połączenia .)
Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,TInner,TResult>) join … in … on … equals …

(Aby uzyskać więcej informacji, zobacz klauzulę join .)
OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby

(Aby uzyskać więcej informacji, zobacz klauzula orderby.)
OrderByDescending<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby … descending

(Aby uzyskać więcej informacji, zobacz klauzulę sortowania .)
Select select

(Aby uzyskać więcej informacji, zobacz klauzulę SELECT .)
SelectMany Wiele from klauzul.

(Aby uzyskać więcej informacji, zobacz z klauzuli.)
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, …

(Aby uzyskać więcej informacji, zobacz klauzulę ORDER BY .)
ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, … descending

(Aby uzyskać więcej informacji, zobacz klauzulę "orderby" .)
Where where

(Aby uzyskać więcej informacji, zobacz where clause.)

Przekształcenia danych za pomocą LINQ

Language-Integrated Query (LINQ) nie dotyczy tylko pobierania danych. Jest to również zaawansowane narzędzie do przekształcania danych. Za pomocą zapytania LINQ można użyć sekwencji źródłowej jako danych wejściowych i zmodyfikować ją na wiele sposobów, aby utworzyć nową sekwencję danych wyjściowych. Sekwencję można modyfikować bez modyfikowania samych elementów przez sortowanie i grupowanie. Ale być może najbardziej zaawansowaną funkcją zapytań LINQ jest możliwość tworzenia nowych typów. Klauzula select tworzy element wyjściowy z elementu wejściowego. Służy do przekształcania elementu wejściowego w element wyjściowy:

  • Połącz wiele sekwencji wejściowych w jedną sekwencję wyjściową, która ma nowy typ.
  • Utwórz sekwencje wyjściowe, których elementy składają się tylko z jednej lub kilku właściwości każdego elementu w sekwencji źródłowej.
  • Utwórz sekwencje wyjściowe, których elementy składają się z wyników operacji wykonywanych na danych źródłowych.
  • Tworzenie sekwencji danych wyjściowych w innym formacie. Można na przykład przekształcić dane z wierszy SQL lub plików tekstowych w format XML.

Te przekształcenia można łączyć na różne sposoby w tym samym zapytaniu. Ponadto sekwencja wyjściowa jednego zapytania może służyć jako sekwencja wejściowa dla nowego zapytania. Poniższy przykład przekształca obiekty w strukturze danych w pamięci na elementy XML.


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

Kod generuje następujące dane wyjściowe XML:

<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>

Aby uzyskać więcej informacji, zobacz Tworzenie drzew XML w języku C# (LINQ to XML).

Możesz użyć wyników jednego zapytania jako źródła danych dla kolejnego zapytania. W tym przykładzie pokazano, jak uporządkować wyniki operacji sprzężenia. To zapytanie tworzy połączenie grup, a następnie sortuje te grupy na podstawie elementu kategorii, który jest nadal w kontekście. Wewnątrz inicjatora typu anonimowego podzapytywanie porządkuje wszystkie pasujące elementy z sekwencji produktów.

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
*/

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

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}");
    }
}

Chociaż można użyć klauzuli orderby z jedną lub więcej sekwencjami źródłowymi przed sprzężeniem, ogólnie tego nie zalecamy. Niektórzy dostawcy LINQ mogą nie zachować tej kolejności po sprzężeniu. Aby uzyskać więcej informacji, zobacz klauzulę łączenia .

Zobacz też