Take และ Skip Operators ใน LINQ
Take(n) - คืน n Element แรกจาก Sequence
Skip(n) - ข้าม n Element แรก คืน Element ที่เหลือ
ร่วมกันใช้สำหรับ Pagination (แบ่งหน้า) และ Windowing
Syntax
// Method Syntax
var result = source.Skip(skipCount).Take(takeCount);
// Query Syntax
var result = (from item in source select item)
.Skip(skipCount)
.Take(takeCount);
ตัวอย่างที่ 1: Skip แยกต่างหาก
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List numbers = new List
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 5, 6, 7, 7, 8, 8 };
Console.WriteLine($"Total Count: {numbers.Count}"); // 28
// Skip 5 ตัวแรก
var skipResult = numbers.Skip(5);
Console.WriteLine($"Skip(5) Count: {skipResult.Count()}"); // 23
Console.WriteLine($"Skip(5) Values: {string.Join(", ", skipResult.Take(10))}..."); // 6,7,8,9,11,1,2,3,4,5
}
}
ผลลัพธ์:
Total Count: 28 Skip(5) Count: 23
ตัวอย่างที่ 2: Take แยกต่างหาก
var takeResult = numbers.Take(5);
Console.WriteLine($"Take(5) Count: {takeResult.Count()}"); // 5
Console.WriteLine($"Take(5) Values: {string.Join(", ", takeResult)}"); // 1,2,3,4,5
ผลลัพธ์:
Take(5) Count: 5
ตัวอย่างที่ 3: Skip และ Take ร่วมกัน (Pagination)
// ข้าม 10 ตัว แล้วเอา 18 ตัว
var skipTakeResult = numbers.Skip(10).Take(18);
Console.WriteLine($"Skip(10).Take(18) Count: {skipTakeResult.Count()}"); // 18
Console.WriteLine($"Values: {string.Join(", ", skipTakeResult)}");
ผลลัพธ์:
Skip(10).Take(18) Count: 18 Values: 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 5, 6, 7, 7, 8, 8
Paging Helper Method
public static class LinqExtensions
{
public static IEnumerable Page(this IEnumerable source, int pageNumber, int pageSize)
{
if (pageNumber < 1) pageNumber = 1;
if (pageSize < 1) pageSize = 10;
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
}
// สำหรับ IQueryable (Database) - Return IQueryable
public static IQueryable Page(this IQueryable source, int pageNumber, int pageSize)
{
if (pageNumber < 1) pageNumber = 1;
if (pageSize < 1) pageSize = 10;
return source.Skip((pageNumber - 1) * pageSize).Take(pageSize);
}
}
// Usage
var page1 = numbers.Page(1, 5); // 1,2,3,4,5
var page2 = numbers.Page(2, 5); // 6,7,8,9,11
var page3 = numbers.Page(3, 5); // 1,2,3,4,5
TakeLast / SkipLast (.NET 6+)
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// TakeLast: เอา 3 ตัวท้าย
var last3 = numbers.TakeLast(3); // 8, 9, 10
// SkipLast: ข้าม 3 ตัวท้าย
var exceptLast3 = numbers.SkipLast(3); // 1, 2, 3, 4, 5, 6, 7
Chunk (.NET 6+) - แบ่งเป็นกลุ่ม
// แบ่งเป็นกลุ่มละ 3
var chunks = numbers.Chunk(3);
// 結果: [[1,2,3], [4,5,6], [7,8,9], [10]]
foreach (var chunk in chunks)
{
Console.WriteLine($"[{string.Join(", ", chunk)}]");
}
SQL Translation (EF Core)
| LINQ | SQL Server | PostgreSQL/MySQL |
|---|---|---|
.Skip(10).Take(5) | OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY | LIMIT 5 OFFSET 10 |
.Take(5) | TOP (5) | LIMIT 5 |
Performance Tips
- List/Array: Skip/Take ใช้ Indexer O(1) - เร็วมาก
- IEnumerable (Iterator): Skip ต้อง Iterate ทุกตัว O(n)
- Database: OFFSET มาก (Deep Paging) ช้า → ใช้ Keyset Paging
- OrderBy จำเป็น: ก่อน Skip/Take ต้อง OrderBy เพื่อผลลัพธ์ Deterministic
Keyset Paging (Cursor-based) - แทน Skip สำหรับ Deep Paging
// แทน Skip(10000).Take(20)
var lastId = 10000; // ID สุดท้ายของหน้าก่อน
var nextPage = await context.Products
.Where(p => p.Id > lastId)
.OrderBy(p => p.Id)
.Take(20)
.ToListAsync();
สรุป
Skip(n)= ข้าม n ตัวแรกTake(n)= เอา n ตัวแรกSkip(a).Take(b)= หน้าที่ (a/b)+1 ขนาด b- ต้อง OrderBy ก่อน เพื่อผลลัพธ์คงที่
- .NET 6+:
TakeLast,SkipLast,Chunk - Deep Paging ใช้ Keyset Paging (WHERE Id > @lastId)