標準查詢運算子概觀

「標準查詢運算子」是形成 LINQ 模式的關鍵字與方法。 C# 語言會定義您在最常見查詢運算式中使用的 LINQ 查詢關鍵字。 編譯器會將使用這些關鍵字的運算式轉譯為對等的方法呼叫。 這兩種形式為同義字。 屬於 System.Linq 命名空間的其他方法沒有對等的查詢關鍵字。 在這些情況下,您必須使用方法語法。 本節涵蓋所有查詢運算子關鍵字。 執行階段和其他 NuGet 套件會新增更多用來與 LINQ 查詢每個版本搭配使用的方法。 本節涵蓋最常見的方法,包括具有查詢關鍵字對等項目的方法。 如需 .NET 執行階段所支援的查詢方法完整清單,請參閱 System.Linq.Enumerable API 文件。 除了這裡所涵蓋的方法之外,此類別還包含串連資料來源的方法,並從資料來源計算單一值,例如總和、平均值或其他值。

這些方法大多會在序列上運作,而序列是指其類型會實作 IEnumerable<T> 介面或 IQueryable<T> 介面的物件。 標準查詢運算子所提供的查詢功能包括篩選、投影、彙總、排序等等。 構成每個集合的方法分別是 EnumerableQueryable 類別的靜態成員。 這些皆定義為其所運作類型的「擴充方法」。

IEnumerable<T>IQueryable<T> 序列之間的差異會決定查詢在執行階段上的執行方式。

針對 IEnumerable<T>,傳回的可列舉物件會擷取傳遞至方法的引數。 傳回的可列舉物件會擷取傳遞至方法的引數。 列舉該物件時,會採用查詢運算子的邏輯,並傳回查詢結果。

針對 IQueryable<T>,查詢會轉譯為運算式樹狀架構。 當資料來源可以最佳化查詢時,運算式樹狀架構可以轉譯為原生查詢。 Entity Framework 之類的程式庫會將 LINQ 查詢轉譯為在資料庫上執行的原生 SQL 查詢。

下列程式碼範例示範如何使用標準查詢運算子來取得序列的資訊。

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 {0}:", 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

可能的話,本節中的查詢會使用一連串的字組或數字作為輸入來源。 對於使用物件間較複雜關聯性的查詢,則會使用下列建立學校模型的來源:

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

每個 Student 都有一個等級、一個主要部門和一系列分數。 Teacher 也有一個 City 屬性,可識別教師持有課程的校園。 Department 具有名稱,以及擔任部門負責人 Teacher 的參考。

查詢運算子的類型

根據傳回單一值還是一系列的值,標準查詢運算子的執行時機會不同。 這些傳回單一值的方法 (例如,AverageSum) 就會立即執行。 傳回序列的方法會延後執行查詢,並傳回可列舉的物件。 您可以使用查詢的輸出序列做為另一個查詢的輸入序列。 在一個查詢中,可以將查詢方法呼叫鏈結在一起,這樣會讓查詢變得更為複雜。

查詢運算子

在 LINQ 查詢中,第一個步驟是指定資料來源。 在 LINQ 查詢中,依序會先出現 from 子句來引進資料來源 (customers) 和範圍變數 (cust)。

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

範圍變數就像 foreach 迴圈中的反覆項目變數,差異在於查詢運算式中沒有實際反覆項目。 執行查詢時,範圍變數會作為 customers 中每個後續項目的參考。 因為編譯器可以推斷 cust 的類型,所以您不需要明確予以指定。 您可以在 let 子句中引進更多範圍變數。 如需詳細資訊,請參閱 let 子句

注意

針對 ArrayList 這類非泛型資料來源,必須明確範圍變數的類型。 如需詳細資訊,請參閱如何使用 LINQ 查詢 ArrayList (C#)from 子句

取得資料來源之後,您就可以在該資料來源上執行任意數目的作業:

查詢運算式語法表

下表列出具有對等查詢運算式子句的標準查詢運算子。

方法 C# 查詢運算式語法
Cast 使用類型明確的範圍變數:

from int i in numbers

(如需詳細資訊,請參閱 from 子句)。
GroupBy group … by

-或-

group … by … into …

(如需詳細資訊,請參閱 group 子句)。
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 …

(如需詳細資訊,請參閱 join 子句)。
Join<TOuter,TInner,TKey,TResult>(IEnumerable<TOuter>, IEnumerable<TInner>, Func<TOuter,TKey>, Func<TInner,TKey>, Func<TOuter,TInner,TResult>) join … in … on … equals …

(如需詳細資訊,請參閱 join 子句)。
OrderBy<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby

(如需詳細資訊,請參閱 orderby 子句)。
OrderByDescending<TSource,TKey>(IEnumerable<TSource>, Func<TSource,TKey>) orderby … descending

(如需詳細資訊,請參閱 orderby 子句)。
Select select

(如需詳細資訊,請參閱 select 子句)。
SelectMany 多個 from 子句。

(如需詳細資訊,請參閱 from 子句)。
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, …

(如需詳細資訊,請參閱 orderby 子句)。
ThenByDescending<TSource,TKey>(IOrderedEnumerable<TSource>, Func<TSource,TKey>) orderby …, … descending

(如需詳細資訊,請參閱 orderby 子句)。
Where where

(如需詳細資訊,請參閱 where 子句)。

使用 LINQ 轉換資料

Language-Integrated Query (LINQ) 不只是擷取資料。 也是功能強大的資料轉換工具。 使用 LINQ 查詢,您可以使用來源序列做為輸入,並在許多方面修改它,以建立新的輸出序列。 藉由排序及群組,您可以修改序列本身,而不修改項目本身。 但或許 LINQ 最強大的功能查詢就是能夠建立新的類型。 select 子句會從輸入元素建立輸出元素。 其可用來將輸入元素轉換成輸出元素:

  • 將多個輸入序列合併為具有新類型的單一輸出序列。
  • 建立輸出序列,使其項目只包含來源序列中每個項目的一或多個屬性。
  • 建立輸出序列,使其項目包含對來源資料執行的作業結果。
  • 以不同格式建立輸出序列。 比方說,您可以將資料從 SQL 資料列或文字檔轉換成 XML。

這些轉換可以用各種方式結合在相同的查詢中。 此外,一個查詢的輸出序列也可用作新查詢的輸入序列。 下列範例會將記憶體中資料結構的物件轉換成 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);

該程式碼會產生下列 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>

如需詳細資訊,請參閱在 C# 中建立 XML 樹狀結構 (LINQ to XML)

您可以使用一個查詢的結果作為後續查詢的資料來源。 本例示範如何排序聯結作業的結果。 此查詢會建立群組聯結,然後根據類別項目排序仍在範圍內的群組。 在匿名型別初始設定式內,子查詢會排序產品序列中的所有相符項目。

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

使用方法語法的對等查詢會顯示在下列程式碼中:

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

雖然您可以在聯結之前使用 orderby 子句搭配一或多個來源序列,但通常不建議這麼做。 某些 LINQ 提供者在聯結後可能不會保留排序。 如需詳細資訊,請參閱 join 子句

另請參閱