Take และ Skip Operators ใน LINQ
Take(n) - เอา n ตัวแรกจาก Sequence
Skip(n) - ข้าม n ตัวแรก แล้วเอาส่วนที่เหลือ
รวมกันใช้ได้: Paging (การแบ่งหน้า)
Syntax
// Skip แล้ว Take (สำคัญ: ลำดับนี้!)
var page = source.Skip(pageSize * (pageNumber - 1)).Take(pageSize);
// หรือ Query Syntax
var page = (from item in source
select item)
.Skip(pageSize * (pageNumber - 1))
.Take(pageSize);
ตัวอย่าง: Paging Implementation
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
// ข้อมูลตัวอย่าง 28 รายการ
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: {numbers.Count}"); // 28
int pageSize = 5;
int totalPages = (int)Math.Ceiling((double)numbers.Count / pageSize);
for (int page = 1; page <= totalPages; page++)
{
var pageData = numbers
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
Console.WriteLine($"\nPage {page} ({pageData.Count} items): {string.Join(", ", pageData)}");
}
}
}
ผลลัพธ์:
Total: 28 Page 1 (5 items): 1, 2, 3, 4, 5 Page 2 (5 items): 6, 7, 8, 9, 11 Page 3 (5 items): 1, 2, 3, 4, 5 Page 4 (5 items): 6, 7, 8, 9, 1 Page 5 (5 items): 2, 3, 5, 6, 7 Page 6 (3 items): 7, 8, 8
ตัวอย่างที่ 2: Skip และ Take แยกกัน
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, 10 };
// Skip 3 ตัวแรก
var skip3 = numbers.Skip(3);
Console.WriteLine($"Skip(3): {string.Join(", ", skip3)}"); // 4,5,6,7,8,9,10
Console.WriteLine($"Count: {skip3.Count()}"); // 7
// Take 4 ตัวแรก
var take4 = numbers.Take(4);
Console.WriteLine($"Take(4): {string.Join(", ", take4)}"); // 1,2,3,4
Console.WriteLine($"Count: {take4.Count()}"); // 4
// Skip 2 Take 3 (หน้าที่ 2 ขนาด 3)
var page2 = numbers.Skip(3).Take(3);
Console.WriteLine($"Skip(3).Take(3): {string.Join(", ", page2)}"); // 4,5,6
}
}
Skip/Take กับ Database (EF Core / LINQ to SQL)
แปลงเป็น SQL OFFSET ... ROWS FETCH NEXT ... ROWS ONLY (SQL Server 2012+) หรือ LIMIT ... OFFSET ... (PostgreSQL/MySQL)
public async Task> GetPagedAsync(int pageNumber, int pageSize)
{
var query = _context.Products.AsQueryable();
var totalCount = await query.CountAsync();
var items = await query
.OrderBy(p => p.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return new PagedResult
{
Items = items,
TotalCount = totalCount,
PageNumber = pageNumber,
PageSize = pageSize
};
}
TakeLast และ SkipLast (.NET 6+)
using System.Linq;
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// TakeLast: เอาสุดท้าย n ตัว
var last3 = numbers.TakeLast(3); // 8, 9, 10
// SkipLast: ข้ามสุดท้าย n ตัว
var skipLast3 = numbers.SkipLast(3); // 1, 2, 3, 4, 5, 6, 7
Chunk (.NET 6+) - แบ่งเป็น Chunk/Yield
// แบ่งเป็นกลุ่มๆ ละ 3
var chunks = numbers.Chunk(3);
// [[1,2,3], [4,5,6], [7,8,9], [10]]
foreach (var chunk in chunks)
{
Console.WriteLine($"Chunk: {string.Join(", ", chunk)}");
}
Performance Considerations
| Scenario | คำแนะนำ |
|---|---|
| In-Memory (List/Array) | Skip/Take เร็ว (O(1) สำหรับ List/Array) |
| IEnumerable (Streaming) | Skip ต้อง Iterate ผ่านทุกตัว (O(n)) |
| Database (EF Core) | แปลงเป็น SQL OFFSET/FETCH - รวดเร็ว แต่ OFFSET มากช้า |
| Large Offset Paging | ใช้ Keyset Paging (WHERE Id > @lastId) แทน Skip |
Keyset Paging (Cursor-based) - เร็วกว่า Skip/Take สำหรับหน้าลึก
// แทน Skip(10000).Take(20) ที่ช้า
var nextPage = await _context.Products
.Where(p => p.Id > lastSeenId) // Cursor
.OrderBy(p => p.Id)
.Take(20)
.ToListAsync();
สรุป
- Skip(n).Take(m) = หน้าที่ (n/m)+1 ขนาด m
- ลำดับสำคัญ: Skip ก่อน Take หลัง
- ใช้ทำ Pagination ได้ง่าย
- Database: แปลงเป็น SQL OFFSET/FETCH
- .NET 6+: มี
TakeLast,SkipLast,Chunk - Deep Paging: ใช้ Keyset Paging (Cursor) แทน Skip