Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

วิธีใช้ Join กับ LINQ และ Lambda ใน C#

การใช้ Join ใน LINQ (C#)

Join ใช้รวมข้อมูลจาก 2 แหล่ง (Collection/Table) ตามเงื่อนไขที่กำหนด คล้าย SQL JOIN

ประเภท Join

  • Inner Join - คืนเฉพาะรายการที่มีคู่ในทั้งสองฝั่ง (Default ของ LINQ Join)
  • Left Join - คืนทุกฝั่งซ้าย + ข้อมูลฝั่งขวาที่มีคู่ (ใช้ GroupJoin + DefaultIfEmpty)
  • Cross Join - Cartesian Product (ใช้ SelectMany)

Syntax: Method Syntax (Lambda) vs Query Syntax

Method Syntax (Lambda)

var result = employees.Join(departments,
    e => e.DepartmentID,      // Outer key selector
    d => d.ID,                // Inner key selector
    (e, d) => new             // Result selector
    {
        EmployeeName = e.Name,
        DepartmentName = d.Name
    });

Query Syntax (คล้าย SQL)

var result = from e in employees
             join d in departments on e.DepartmentID equals d.ID
             select new
             {
                 EmployeeName = e.Name,
                 DepartmentName = d.Name
             };

ตัวอย่างครบ: Inner Join พร้อม Class Definition

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        // Method Syntax (Lambda)
        var resultLambda = Employee.GetAllEmployees()
            .Join(Department.GetAllDepartments(),
                e => e.DepartmentID,
                d => d.ID,
                (e, d) => new
                {
                    EmployeeName = e.Name,
                    DepartmentName = d.Name
                });
        
        Console.WriteLine("=== Lambda Syntax ===");
        foreach (var emp in resultLambda)
        {
            Console.WriteLine($"{emp.EmployeeName}\t{emp.DepartmentName}");
        }
        
        // Query Syntax
        var resultQuery = from e in Employee.GetAllEmployees()
                          join d in Department.GetAllDepartments()
                          on e.DepartmentID equals d.ID
                          select new
                          {
                              EmployeeName = e.Name,
                              DepartmentName = d.Name
                          };
        
        Console.WriteLine("\n=== Query Syntax ===");
        foreach (var emp in resultQuery)
        {
            Console.WriteLine($"{emp.EmployeeName}\t{emp.DepartmentName}");
        }
    }
}

public class Employee
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int DepartmentID { get; set; }
    
    public static List GetAllEmployees()
    {
        return new List
        {
            new Employee { ID = 1, Name = "A", DepartmentID = 1 },
            new Employee { ID = 2, Name = "B", DepartmentID = 2 },
            new Employee { ID = 3, Name = "B", DepartmentID = 1 },
            new Employee { ID = 4, Name = "V", DepartmentID = 1 },
            new Employee { ID = 5, Name = "F", DepartmentID = 2 },
            new Employee { ID = 6, Name = "R", DepartmentID = 2 },
            new Employee { ID = 7, Name = "TT", DepartmentID = 1 },
            new Employee { ID = 8, Name = "YY", DepartmentID = 1 },
            new Employee { ID = 9, Name = "WW", DepartmentID = 2 },
            new Employee { ID = 10, Name = "QQ" }  // ไม่มี DepartmentID
        };
    }
}

public class Department
{
    public int ID { get; set; }
    public string Name { get; set; }
    
    public static List GetAllDepartments()
    {
        return new List
        {
            new Department { ID = 1, Name = "IT" },
            new Department { ID = 2, Name = "HR" },
            new Department { ID = 3, Name = "Contract" }  // ไม่มีพนักงาน
        };
    }
}

ผลลัพธ์:

=== Lambda Syntax ===
A	IT
B	HR
B	IT
V	IT
F	HR
R	HR
TT	IT
YY	IT
WW	HR

=== Query Syntax ===
A	IT
B	HR
B	IT
V	IT
F	HR
R	HR
TT	IT
YY	IT
WW	HR

หมายเหตุ: พนักงาน "QQ" (ID=10) ไม่ปรากฏ เพราะไม่มี DepartmentID ตรงกับ Department ใดๆ (Inner Join) และ Department "Contract" (ID=3) ก็ไม่ปรากฏ เพราะไม่มีพนักงาน

Left Join (รองรับฝั่งซ้ายที่ไม่มีคู่)

// Left Join: พนักงานทุกคน แม้ไม่มี Department
var leftJoin = from e in Employee.GetAllEmployees()
               join d in Department.GetAllDepartments()
               on e.DepartmentID equals d.ID into deptGroup
               from d in deptGroup.DefaultIfEmpty()  // Key point!
               select new
               {
                   EmployeeName = e.Name,
                   DepartmentName = d?.Name ?? "No Department"
               };

// ผลลัพธ์จะมี QQ - No Department

Join หลายเงื่อนไข (Composite Key)

// ใช้ Anonymous Type สำหรับ Key หลายคอลัมน์
var result = orders.Join(orderDetails,
    o => new { o.OrderID, o.CustomerID },      // Composite key
    d => new { d.OrderID, d.CustomerID },
    (o, d) => new { o.OrderID, o.CustomerID, d.ProductID, d.Quantity });

GroupJoin (Hierarchical Data)

// Department พร้อมรายชื่อพนักงาน (1-N)
var deptWithEmps = departments.GroupJoin(employees,
    d => d.ID,
    e => e.DepartmentID,
    (d, emps) => new
    {
        DepartmentName = d.Name,
        Employees = emps.Select(e => e.Name).ToList()
    });

foreach (var dept in deptWithEmps)
{
    Console.WriteLine($"Department: {dept.DepartmentName}");
    foreach (var emp in dept.Employees)
    {
        Console.WriteLine($"  - {emp}");
    }
}

Performance Tips

  • Key Selector ควรเป็น Primitive Type (int, string, Guid) เร็วกว่า Object
  • Index/Hash Join - LINQ to Objects ใช้ Hash Join อัตโนมัติ (O(n+m))
  • LINQ to SQL/EF Core - แปลงเป็น SQL JOIN จริงใน Database
  • AsParallel() - สำหรับข้อมูลเยอะมาก (PLINQ)

สรุป

  • Inner Join: Join() หรือ join ... on ... equals
  • Left Join: GroupJoin() + DefaultIfEmpty() หรือ join ... into ... from ... DefaultIfEmpty()
  • Composite Key: ใช้ Anonymous Type new { Prop1, Prop2 }
  • Query Syntax อ่านง่ายสำหรับ Join 複雜, Lambda ยืดหยุ่นกว่าสำหรับ Chaining