class Student { public int Id; public string Name; }
class Course { public int Id; public string Name; }
class Score { public int StudentId; public int CourseId; public double Value; }
var students = new List<Student> {
new Student{Id=1,Name="Andy"}, new Student{Id=2,Name="Bill"},
new Student{Id=3,Name="Cindy"}, new Student{Id=4,Name="Dark"}
};
var courses = new List<Course> {
new Course{Id=1,Name="C語言"}, new Course{Id=2,Name="生物物理"},
new Course{Id=3,Name="編譯原理"}, new Course{Id=4,Name="財務"},
new Course{Id=5,Name="大學英語"}
};
var scores = new List<Score> {
new Score{StudentId=1,CourseId=1,Value=78}, new Score{StudentId=1,CourseId=2,Value=60},
new Score{StudentId=1,CourseId=3,Value=92}, new Score{StudentId=1,CourseId=4,Value=85},
new Score{StudentId=1,CourseId=5,Value=55.5}, new Score{StudentId=2,CourseId=1,Value=59},
new Score{StudentId=2,CourseId=2,Value=78}, new Score{StudentId=2,CourseId=3,Value=86},
new Score{StudentId=3,CourseId=2,Value=60}, new Score{StudentId=4,CourseId=4,Value=96},
new Score{StudentId=5,CourseId=5,Value=78}
};
var groups = from s in scores
group s by s.StudentId into g
select g; // IEnumerable<IGrouping<int, Score>>
foreach (var g in groups)
{
Console.WriteLine($"\n學生 {g.Key}:");
foreach (var s in g) Console.WriteLine($" {s.CourseId}: {s.Value}");
}
var result = from s in scores
group s by s.StudentId into g
select new { StudentId = g.Key, Scores = g };
// 等價擴展方法
var result = scores.GroupBy(s => s.StudentId,
(key, group) => new { StudentId = key, Scores = group });
var query = from sc in scores
join st in students on sc.StudentId equals st.Id
join co in courses on sc.CourseId equals co.Id
select new { Student = st.Name, Course = co.Name, Score = sc.Value };
/* 結果:
{ Student = Andy, Course = C語言, Score = 78 }
{ Student = Andy, Course = 生物物理, Score = 60 }
...
*/
var query = scores
.Join(students, sc => sc.StudentId, st => st.Id,
(sc, st) => new { st.Name, sc.Value, sc.CourseId })
.Join(courses, x => x.CourseId, co => co.Id,
(x, co) => new { Student = x.Name, Course = co.Name, Score = x.Value });
當右側無匹配時,右側字段為默認值 (null/0)。
var students2 = new List<Student> {
new Student{Id=5,Name="Erik"}, new Student{Id=6,Name="Frank"} // Frank 無成績
};
var leftJoin = from st in students2
join sc in scores on st.Id equals sc.StudentId into scGroup
from sc in scGroup.DefaultIfEmpty()
select new { Student = st.Name, Score = sc == null ? 0 : sc.Value };
/* 結果:
{ Student = Erik, Score = 78 }
{ Student = Frank, Score = 0 } // 保留無成績學生
*/
var leftJoin = students2
.GroupJoin(scores, st => st.Id, sc => sc.StudentId,
(st, scGroup) => new { st, scGroup })
.SelectMany(x => x.scGroup.DefaultIfEmpty(),
(x, sc) => new { Student = x.st.Name, Score = sc == null ? 0 : sc.Value });
生成所有學生×課程組合,作為成績錄入模板。
var template = from st in students
from co in courses
select new { Student = st.Name, Course = co.Name, Score = (double?)null };
/* 結果: 4學生 × 5課程 = 20 行,Score 全為 null */
var template = students.SelectMany(st =>
courses.Select(co => new { Student = st.Name, Course = co.Name, Score = (double?)null }));
相當於 GroupBy + Join 結合,直接得到分組後的集合。
var grouped = students
.GroupJoin(scores, st => st.Id, sc => sc.StudentId,
(st, scGroup) => new { Student = st.Name, Scores = scGroup });
/* 結果結構:
{ Student = Andy, Scores = [Score1, Score2, ...] }
{ Student = Bill, Scores = [Score1, Score2, ...] }
...
*/
| 連接類型 | 查詢語法關鍵字 | 擴展方法 | 特點 |
|---|---|---|---|
| 內連接 | join ... on ... equals ... | Join | 雙方都有匹配才保留 |
| 左外連接 | join ... into ... from ... DefaultIfEmpty() | GroupJoin + SelectMany | 保留左側全部,右側無匹配為默認值 |
| 笛卡爾積 | from a in A from b in B | SelectMany | 所有組合,無條件 |
| 分組連接 | (無直接關鍵字) | GroupJoin | 左側每項對應右側集合 |
DefaultIfEmpty() 時,引用類型結果為 null,值類型為 0,需顯式處理掌握這五種連接模式,LINQ 即可覆蓋 SQL 90% 以上的查詢場景。