共用方式為


使用語言整合查詢 (LINQ) 以 C# 撰寫查詢的教學指南

在本教學課程中,您會建立數據源,並撰寫數個 LINQ 查詢。 您可以實驗查詢表達式,並查看結果的差異。 本逐步解說示範用來撰寫 LINQ 查詢表達式的 C# 語言功能。 您可以跟著做,並自行建置應用程式並實驗查詢。 本文假設您已安裝最新的 .NET SDK。 如果沒有,請移至 [.NET 下載] 頁面 ,並在您的計算機上安裝最新版本。

首先,建立應用程式。 從主控台輸入下列命令:

dotnet new console -o WalkthroughWritingLinqQueries

或者,如果您想要 Visual Studio,請建立名為 WalkthroughWritingLinqQueries 的新控制台應用程式。

建立記憶體內部數據源

第一個步驟是建立查詢的數據源。 查詢的數據源是簡單的記錄清單 Student 。 每個 Student 記錄都有名字、系列名稱和整數數位,代表其在類別中的測試分數。 新增名為 students.cs 的新檔案,並將下列程式代碼複製到該檔案:

namespace WalkthroughWritingLinqQueries;

public record Student(string First, string Last, int ID, int[] Scores);

請注意下列特性:

  • 記錄 Student 是由自動實作的屬性所組成。
  • 清單中的每個學生都會使用主要建構函式初始化。
  • 每個學生的分數序列會以主要建構函式初始化。

接下來,建立一連串 Student 記錄,做為此查詢的來源。 開啟 Program.cs,並移除下列未定案程序代碼:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

將它替換為以下程式代碼,以建立 Student 記錄的序列:

using WalkthroughWritingLinqQueries;

// Create a data source by using a collection initializer.
IEnumerable<Student> students =
[
    new Student(First: "Svetlana", Last: "Omelchenko", ID: 111, Scores: [97, 92, 81, 60]),
    new Student(First: "Claire",   Last: "O'Donnell",  ID: 112, Scores: [75, 84, 91, 39]),
    new Student(First: "Sven",     Last: "Mortensen",  ID: 113, Scores: [88, 94, 65, 91]),
    new Student(First: "Cesar",    Last: "Garcia",     ID: 114, Scores: [97, 89, 85, 82]),
    new Student(First: "Debra",    Last: "Garcia",     ID: 115, Scores: [35, 72, 91, 70]),
    new Student(First: "Fadi",     Last: "Fakhouri",   ID: 116, Scores: [99, 86, 90, 94]),
    new Student(First: "Hanying",  Last: "Feng",       ID: 117, Scores: [93, 92, 80, 87]),
    new Student(First: "Hugo",     Last: "Garcia",     ID: 118, Scores: [92, 90, 83, 78]),

    new Student("Lance",   "Tucker",      119, [68, 79, 88, 92]),
    new Student("Terry",   "Adams",       120, [99, 82, 81, 79]),
    new Student("Eugene",  "Zabokritski", 121, [96, 85, 91, 60]),
    new Student("Michael", "Tucker",      122, [94, 92, 91, 91])
];
  • 學生序列會使用集合表示式初始化。
  • 記錄 Student 類型會保存所有學生的靜態清單。
  • 某些建構函式呼叫會使用 具名自變數 來釐清哪些自變數符合哪個建構函式參數。

請嘗試將更多具有不同測試分數的學生新增至學生清單,以更熟悉到目前為止的程序代碼。

建立查詢

接下來,您會建立第一個查詢。 當您執行查詢時,會產生第一次測試分數大於90的所有學生清單。 因為已選取整個 Student 物件,因此查詢的類型為 IEnumerable<Student>。 雖然程序代碼也可以使用 var 關鍵詞來使用隱含類型,但明確輸入是用來清楚說明結果。 (如需有關 var 的詳細資訊,請參閱 隱含型別區域變數。)在建立學生序列的程式碼之後,將下列程式碼新增至 Program.cs

// Create the query.
// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
    from student in students
    where student.Scores[0] > 90
    select student;

查詢的範圍變數 student會做為來源中每個 Student 變數的參考,為每個物件提供成員存取權。

執行查詢

現在撰寫迴圈 foreach ,讓查詢執行。 傳回序列中的每個項目都會透過迴圈中的 foreach 反覆專案變數來存取。 這個變數的類型為 Student,且查詢變數的類型相容, IEnumerable<Student>。 新增下列程式代碼之後,請建置並執行應用程式,以查看 主控台 視窗中的結果。

// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
    Console.WriteLine($"{student.Last}, {student.First}");
}

// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

若要進一步精簡查詢,您可以在 子句中 where 結合多個布爾條件。 下列程式代碼會新增條件,讓查詢傳回第一個分數超過 90 且最後一個分數小於 80 的學生。 子 where 句應該類似下列程式碼。

where student.Scores[0] > 90 && student.Scores[3] < 80  

請嘗試上述 where 子句,或自行試驗其他篩選條件。 如需詳細資訊,請參閱 where 子句

排序查詢結果

結果如果依某種順序排列,會更容易掃描。 您可以依來源元素中的任何可存取欄位來排序傳回的序列。 例如,下列 orderby 子句會根據每個學生的姓氏,依字母順序從 A 到 Z 排序結果。 請將下列 orderby 子句新增至查詢,緊接在 where 語句之後,並在 select 語句之前:

orderby student.Last ascending

現在,請變更 orderby 子句,使其根據第一次測試的分數,依反向順序排序結果,從最高分數到最低分數。

orderby student.Scores[0] descending

WriteLine變更格式字串,以便您可以看到分數:

Console.WriteLine($"{student.Last}, {student.First} {student.Scores[0]}");

如需詳細資訊,請參閱 排序子句

將結果分組

群組是查詢表達式的強大功能。 具有 group 子句的查詢會產生一連串的群組,而每個群組本身都包含 Key ,以及包含該群組所有成員的序列。 下列新查詢會使用姓氏的第一個字母作為索引鍵,將學生分組。

IEnumerable<IGrouping<char, Student>> studentQuery =
    from student in students
    group student by student.Last[0];

查詢的類型已變更。 它現在會產生一連串的群組,將 char 類型視為鍵,還有一連串的 Student 物件。 執行循環中的 foreach 程式碼也必須變更:

foreach (IGrouping<char, Student> studentGroup in studentQuery)
{
    Console.WriteLine(studentGroup.Key);
    foreach (Student student in studentGroup)
    {
        Console.WriteLine($"   {student.Last}, {student.First}");
    }
}
// Output:
// O
//   Omelchenko, Svetlana
//   O'Donnell, Claire
// M
//   Mortensen, Sven
// G
//   Garcia, Cesar
//   Garcia, Debra
//   Garcia, Hugo
// F
//   Fakhouri, Fadi
//   Feng, Hanying
// T
//   Tucker, Lance
//   Tucker, Michael
// A
//   Adams, Terry
// Z
//   Zabokritski, Eugene

執行應用程式,並在 主控台 視窗中檢視結果。 如需詳細資訊,請參閱 組別子句

明確地進行IEnumerablesIGroupings的編碼很快就會變得乏味。 使用 var更方便地撰寫相同的查詢和foreach迴圈。 關鍵詞 var 不會變更物件的類型;它只會指示編譯程式推斷類型。 將和 studentQuery 反覆運算變數 group 的類型變更為 var ,然後重新執行查詢。 在內部 foreach 迴圈中,反覆專案變數仍會輸入為 Student,而查詢的運作方式與之前相同。 將 student 反覆專案變數變更為 var ,然後再次執行查詢。 您會看到您得到完全相同的結果。

IEnumerable<IGrouping<char, Student>> studentQuery =
    from student in students
    group student by student.Last[0];

foreach (IGrouping<char, Student> studentGroup in studentQuery)
{
    Console.WriteLine(studentGroup.Key);
    foreach (Student student in studentGroup)
    {
        Console.WriteLine($"   {student.Last}, {student.First}");
    }
}

如需 var 的詳細資訊,請參閱 隱含型別局部變數

依其索引鍵值排序群組

上一個查詢中的群組不是依字母順序排列。 您可以在group子句之後提供orderby子句。 但是若要使用 orderby 子句,您必須先有一個標識符,作為 group 子句所建立群組的參考。 您可以使用 關鍵字來提供識別碼 into ,如下所示:

var studentQuery4 =
    from student in students
    group student by student.Last[0] into studentGroup
    orderby studentGroup.Key
    select studentGroup;

foreach (var groupOfStudents in studentQuery4)
{
    Console.WriteLine(groupOfStudents.Key);
    foreach (var student in groupOfStudents)
    {
        Console.WriteLine($"   {student.Last}, {student.First}");
    }
}

// Output:
//A
//   Adams, Terry
//F
//   Fakhouri, Fadi
//   Feng, Hanying
//G
//   Garcia, Cesar
//   Garcia, Debra
//   Garcia, Hugo
//M
//   Mortensen, Sven
//O
//   Omelchenko, Svetlana
//   O'Donnell, Claire
//T
//   Tucker, Lance
//   Tucker, Michael
//Z
//   Zabokritski, Eugene

執行此查詢,而且群組現在會依字母順序排序。

您可以使用 let 關鍵詞來介紹查詢表達式中任何表達式結果的標識碼。 此標識符可能很方便,如下列範例所示。 它也可以藉由儲存表達式的結果來增強效能,因此不需要多次計算。

// This query returns those students whose
// first test score was higher than their
// average score.
var studentQuery5 =
    from student in students
    let totalScore = student.Scores[0] + student.Scores[1] +
        student.Scores[2] + student.Scores[3]
    where totalScore / 4 < student.Scores[0]
    select $"{student.Last}, {student.First}";

foreach (string s in studentQuery5)
{
    Console.WriteLine(s);
}

// Output:
// Omelchenko, Svetlana
// O'Donnell, Claire
// Mortensen, Sven
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

如需詳細資訊,請參閱有關條款的文章let

在查詢表達式中使用方法語法

LINQ 中的查詢語法和方法語法中所述,某些查詢作業只能使用方法語法來表示。 下列程式代碼會計算來源序列中每個 Student 的總分數,然後在該查詢的結果上呼叫 Average() 方法,以計算 類別的平均分數。

var studentQuery =
    from student in students
    let totalScore = student.Scores[0] + student.Scores[1] +
        student.Scores[2] + student.Scores[3]
    select totalScore;

double averageScore = studentQuery.Average();
Console.WriteLine($"Class average score = {averageScore}");

// Output:
// Class average score = 334.166666666667

在 select 子句中進行轉換或投影

查詢通常會產生與來源序列中元素不同的序列。 刪除或批注化您先前的查詢和執行迴圈,並將它取代為下列程序代碼。 查詢會傳回一連串字串(而非 Students),而這個事實會反映在迴圈中 foreach

IEnumerable<string> studentQuery =
    from student in students
    where student.Last == "Garcia"
    select student.First;

Console.WriteLine("The Garcias in the class are:");
foreach (string s in studentQuery)
{
    Console.WriteLine(s);
}

// Output:
// The Garcias in the class are:
// Cesar
// Debra
// Hugo

本逐步解說稍早的程式代碼表示平均類別分數約為 334。 若要產生其總分數大於類別平均值的 Students 序列,以及它們的 Student ID,您可以在 語句中使用 select 匿名型別:

var aboveAverageQuery =
    from student in students
    let x = student.Scores[0] + student.Scores[1] +
        student.Scores[2] + student.Scores[3]
    where x > averageScore
    select new { id = student.ID, score = x };

foreach (var item in aboveAverageQuery)
{
    Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score);
}

// Output:
// Student ID: 113, Score: 338
// Student ID: 114, Score: 353
// Student ID: 116, Score: 369
// Student ID: 117, Score: 352
// Student ID: 118, Score: 343
// Student ID: 120, Score: 341
// Student ID: 122, Score: 368