在本教學課程中,您會建立數據源,並撰寫數個 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
執行應用程式,並在 主控台 視窗中檢視結果。 如需詳細資訊,請參閱 組別子句。
明確地進行IEnumerables
IGroupings
的編碼很快就會變得乏味。 使用 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