Skip to content

Latest commit

 

History

History
592 lines (451 loc) · 32.5 KB

linq-demo.md

File metadata and controls

592 lines (451 loc) · 32.5 KB
description
🤔 คำสั่งของ LINQ ที่ได้ใช้บ่อยๆมีไรบ้างนะ

พระคัมภีร์การใช้คำสั่ง LINQ

บทความนี้เป็นบทความที่แยกออกมาจากเรื่อง LINQ ซึ่งเป็นหนึ่งในคำสั่งเทพเจ้าของสาย .NET ซึ่งมันจะทำให้ developer ทำงานได้สบายลงแบบฝุดๆ ดังนั้นใครยังไม่รู้เรื่อง LINQ ให้กลับไปอ่านบทความนี้ก่อนเน่อ Saladpuk - LINQ 101

Filtering Data

Where - เป็นการเลือกเอาเฉพาะข้อมูลที่เราสนใจออกมา เช่น มี data source เป็นเลข 1~100 แล้วต้องการเอาเฉพาะเลขที่ 5 และ 7 หารลงตัวออกมา ก็จะเขียนออกมาได้เป็นแบบนี้

var collection = Enumerable.Range(1, 100);
var qry = collection.Where(it => it % 5 == 0 && it % 7 == 0);
// ผลลัพท์: { 35, 70 }

Projection Operations

Select - เป็นการเลือกว่า data source ที่เราไปดึงข้อมูลมา เราจะดัดแปลงแก้ไข หรือ เลือกเอาเฉพาะข้อมูลบางส่วนออกมาใช้

เช่นมี collection เป็นเลข 1~5 ตอนที่เราจะเอามาทำงานด้วยเราจะแก้ให้มันถูก คูณด้วย 10 ก่อนค่อยเอามาใช้งาน ก็จะเขียนได้แบบนี้

var collection = new int[] { 1, 2, 3, 4, 5 };
var qry = collection.Select(it => it * 10);
// ผลลัพท์: { 10, 20, 30, 40, 50 }

หรือ จะให้มันเปลี่ยนเป็นข้อมูลอีกประเภทนึงเลยก็ได้

var qry = collection.Select(it => new Student{ Id = it });
public class Student
{
    public int Id { get; set; }
}

ส่วนถ้าข้อมูลใน data source มันวุ่นวายเกินไป เราก็สามารถเลือกแค่บางส่วนของมันมาใช้ก็ได้นะ เช่น เราอยากได้แค่ Name ที่อยู่ใน collection มาใช้เท่านั้น ก็เขียนเป็นแบบนี้ได้

var collection = new[]
{
    new { Id = 1, Name = "A", Age = 10 },
    new { Id = 2, Name = "B", Age = 15 },
    new { Id = 3, Name = "C", Age = 20 },
};
var qry = collection.Select(it => it.Name);
// ผลลัพท์: { "A", "B", "C" }

SelectMany - เป็นการเลือกเข้าไปถึงหน่วยย่อยของ collection ที่ซ้อนภายใน collection อีกทีนึง

var collection = new[]
{
    new [] { 1, 2, 3, 4 },
    new [] { 5, 6, 7, 8 },
};
var qry = collection.SelectMany(it => it);

{% hint style="info" %} แนะนำให้อ่าน
คำสั่ง SelectMany สำหรับคนที่พึ่งหัดใช้ LINQ อาจจะ งงๆ หน่อยแต่ถ้าเราได้ทำงานร่วมกับพวก collection ซ้อน collection แล้วล่ะก็ควรจะทำความเข้าใจมันเอาไว้นะ ซึ่งอ่านได้จากลิงค์นี้เลย
Microsoft document - Projection Operations {% endhint %}

Element Operations

ในบางทีเราอยากจะทำงานกับข้อมูลแค่ตัวใดตัวหนึ่งหรือส่วนหนึ่งที่อยู่ใน collection เราก็สามารถใช้คำสั่งที่อยู่ด้านล่างได้ เช่น เรามี data source ที่มีข้อมูลเป็นเลข 1~100

var collection = Enumerable.Range(1, 100);

First - เอาเฉพาะตัวแรกออกมา

var result = collection.First();
// ผลลัพท์: { 1 }

FirstOrDefault - เหมือนกับ First ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา

var result = collection.FirstOrDefault();
// ผลลัพท์: { 1 }

Last - เอาเฉพาะตัวสุดท้ายออกมา

var result = collection.Last();
// ผลลัพท์: { 100 }

LastOrDefault - เหมือนกับ Last ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา

var result = collection.LastOrDefault();
// ผลลัพท์: { 100 }

ElementAt - เป็นการดึงค่าที่อยู่ใน index ที่กำหนดออกมา

var result = collection.ElementAt(3);
// ผลลัพท์: { 4 }

ElementAtOrDefault - เหมือนกับ ElementAt ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา

var result = collection.ElementAtOrDefault(9999);
// ผลลัพท์: { 0 }

{% hint style="danger" %} อันตราย
ถ้า data source เป็น collection ว่าง แล้วไปใช้คำสั่งพวก First, Last, ElementAt มันจะทำให้เกิด Exception ได้ครับ ดังนั้นโดยปรกติผมจะแนะนำให้ใช้คำสั่ง FirstOrDefault, LastOrDefault, ElementAtOrDefault แทนมากกว่า เพราะค่า overhead ในการจัดการกับ error มันสูงกว่าครับ {% endhint %}

Partitioning Data

เวลาที่เราทำงานกับ data source ปริมาณมากๆ เราสามารถที่จะทำการแบ่งข้อมูลออกเป็นส่วนๆ เพื่อให้ง่ายในการทำงานได้ เช่น มี collection ตัวเลข 1~100 อยู่ตามด้านล่าง

var collection = Enumerable.Range(1, 100);

Take - เป็นการสั่งให้ดึงข้อมูลจาก data source ออกมาเท่าที่เรากำหนดไว้ เช่น เราอยากดึงข้อมูลมาแค่ 5 ตัวแรกก่อน เราก็จะเขียนได้ว่า

var qry = collection.Take(5);
// ผลลัพท์: { 1, 2, 3, 4, 5 }

TakeLast - เหมือนกับ Take แต่จะดึงมาจากด้านหลังสุด เช่น อยากจะดึงข้อมูล 5 ตัวจากด้านหลังสุดออกมา

var qry = collection.TakeLast(5);
// ผลลัพท์: { 96, 97, 98, 99, 100 }

TakeWhile - เป็นการสั่งให้มันดึงข้อมูลจาก data source ออกมาเรื่อยๆจนกว่าจะเจอตัวแรกที่ทำให้เงื่อนไขไม่เป็นจริง เช่น ให้ดึงมาเรื่อยๆถ้าเลขที่ดึงมามันยังน้อยกว่า 8

var qry = collection.TakeWhile(it => it < 8);
// ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7 }

Skip - สั่งให้ข้ามข้อมูลเท่ากับที่เรากำหนด เช่น เราต้องการข้ามข้อมูล 4 ตัวแรกไป

var qry = collection.Skip(4);
// ผลลัพท์: { 5, 6, 7 ... 100 }

SkipLast - เหมือนกับ Skip ต่างกันแค่มันจะข้ามเฉพาะตัวด้านหลังสุด เช่น อยากจะข้ามข้อมูล 4 ตัวสุดท้ายไป

var qry = collection.SkipLast(4);
// ผลลัพท์: { 1, 2, 3 ... 96 }

SkipWhile - เป็นการสั่งให้มันข้ามข้อมูลไปเรื่อยๆ ถ้าเงื่อนไขยังเป็นจริงอยู่ และจะหยุดข้ามเมื่อเจอข้อมูลตัวแรกที่ไม่ตรงเงื่อนไข เช่น อยากจะข้ามไปเรื่อยๆจนกว่าจะเจอตัวแรกที่มากกว่า 50

var qry = collection.SkipWhile(it => it < 50);
// ผลลัพท์: { 50, 51, 52 ... 100 }

Set Operations

เราสามารถทำงานกับ data source ที่เป็น 2 กลุ่มให้มาทำงานร่วมกันได้ 3 แบบคือ

เช่นเรามีข้อมูลกลุ่ม a กับกลุ่ม b เป็นแบบนี้

var a = new[] { 1, 2, 3, 4, 5 };
var b = new[] { 4, 5, 6, 7, 8 };

Intersect - ตามรูปเลยคือ เอาเฉพาะที่มันเหมือนกันออกมา

var intersect = a.Intersect(b);
// ผลลัพท์: { 4, 5 }

Union - ตามรูปเลยคือ เอาทั้งสองกลุ่มมารวมกัน

var union = a.Union(b);
// ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7, 8 }

Except - ตามรูปเลยคือ เอาเฉพาะของที่ไม่ซ้ำกับอีกกลุ่มออกมา

var except = a.Except(b);
// ผลลัพท์: { 1, 2, 3 }

Distinct - เป็นการตัดตัวซ้ำทิ้ง

var collection = new[] { 1, 1, 2, 2, 3, 4, 4, 3 };
var qry = collection.Distinct();
// ผลลัพท์: { 1, 2, 3, 4 }

Sorting Data

การเรียงลำดับเราทำได้ 3 แบบ น้อยไปมาก มากไปน้อย และ กลับด้านข้อมูล เช่นเรามี data source เป็นแบบนี้

var collection = new int[] { 7, 5, 2, 6, 4, 1, 3 };

OrderBy - เรียงลำดับจากน้อยไปมาก หรือถ้าเป็นตัวอักษรจะเป็นการเรียงจาก a~Z

var ascending = collection.OrderBy(it => it);
// ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7 }

OrderByDescending - เรียงลำดับจากมากไปน้อย

var descending = collection.OrderByDescending(it => it);
// ผลลัพท์: { 7, 6, 5, 4, 3, 2, 1 }

Reverse - เรียงลำดับแบบกลับด้าน ขวาไปซ้าย แทน

var reverse = collection.Reverse();
// ผลลัพท์: { 3, 1, 4, 6, 2, 5, 7 }

ThenBy และ ThenByDescending - แต่ถ้าข้อมูลมีความซับซ้อนมากขึ้น เราสามารถกำหนดความสำคัญในการเรียงลำดับได้ด้วย เช่น เรียงลำดับจากคะแนนน้อยไปมาก แต่ถ้าคะแนนเท่ากันให้เรียงจากชื่อตามลำดับตัวอักษรก็จะเขียนแบบนี้ได้

var collection = new[]
{
    new { Score = 7, Name = "B" },
    new { Score = 3, Name = "A" },
    new { Score = 7, Name = "A" },
    new { Score = 4, Name = "A" },
    new { Score = 3, Name = "C" },
};

// น้อยไปมาก และ ตามลำดับตัวอักษร
var ascending = collection
                .OrderBy(it => it.Score)
                .ThenBy(it => it.Name);

// มากไปน้อย และ ตามลำดับตัวอักษร
var descending = collection
                .OrderByDescending(it => it)
                .ThenBy(it => it.Name);

Quantifier Operations

เราสามารถหาผลลัพท์จากข้อมูลใน collection ได้เช่น มีบางตัวไหม? หรือ ทุกตัวเป็นแบบนี้ไหม? อาจจะฟังแล้ว งงๆ ไปดูตัวอย่างเลยดีกว่า โดยสมมุติว่าผมมี data source เป็นแบบนี้

var collection = new int[] { 2, 4, 6, 8, 10 };

Any - ถามว่ามีซักตัวไหมที่เป็นแบบนี้ เช่น อยากรู้ว่ามีซักตัวไหมใน collection ที่มีค่ามากกว่า 9 ก็สามารถเขียนเป็น

var any = collection.Any(it => it > 9); // true

All - ถามว่าทุกตัวเป็นแบบนี้หรือเปล่า เช่น อยากเช็คว่าทุกตัวใน collection มากกว่า 5 หรือเปล่า

var all = collection.All(it => it > 5); // false

Contains - ถามว่าภายในนั้นมีตัวนี้อยู่หรือเปล่า เช่น collection นั้นมีเลข 8 อยู่ในนั้นหรือเปล่า

var contain = collection.Contains(8); // true

Grouping Data

GroupBy - สั่งให้มันจัดกลุ่มของข้อมูลได้ เช่น มี collection ของคนหลายๆคน แล้วเราอยากให้จัดกลุ่มคนตามอายุ เราก็จะเขียนได้ว่า

var collection = new[]
{
    new { Name = "A", Age = 15 },
    new { Name = "B", Age = 7 },
    new { Name = "C", Age = 7 },
    new { Name = "D", Age = 15 },
    new { Name = "E", Age = 9 },
};
var qry = collection.GroupBy(it => it.Age);

/* ผลลัพท์
15
{ Name = A, Age = 15 }
{ Name = D, Age = 15 }
7
{ Name = B, Age = 7 }
{ Name = C, Age = 7 }
9
{ Name = E, Age = 9 }
*/

Generation Operations

ถ้าเราต้องการสร้างข้อมูลที่เป็น collection ขึ้นมาแบบง่ายๆ เราก็สามารถใช้ LINQ ช่วยสร้างได้

Range - สร้างชุดตัวเลขออกมา เช่น อยากได้ collection ตัวเลขตั้งแต่ 1 ถึง 100

var qry = Enumerable.Range(1, 100);
// ผลลัพท์: { 1, 2, 3 ... 100 }

Empty - สร้าง collection ว่างออกมา เช่น เราอยากได้ collection ของตัวเลข แต่ไม่ต้องมีข้อมูลอะไรอยู่ข้างในนะ

var qry = Enumerable.Empty<int>();
// ผลลัพท์: { }

DefaultIfEmpty - ถ้าเราต้องไปทำงานกับ collection ตัวเลขซักตัว แต่ถ้า collection นั้นมันเป็นค่าว่าง เราจะกำหนดค่า 9 ให้มันไปใช้แทน

var collection = Enumerable.Empty<int>();
var qry = collection.DefaultIfEmpty(9);
// ผลลัพท์: { 9 }

Repeat - สร้างชุดข้อมูลซ้ำๆกันออกมา เช่น อยากได้ collection เลข 5 ซ้ำกัน 3 ตัว ก็เขียนแบบนี้ได้

var qry = Enumerable.Repeat(5, 3);
// ผลลัพท์: { 5, 5, 5 }

Converting Data Types

เราสามารถแปลง data source ของเราจาก data type นึงไปยังอีก data type นึงก็ได้นะ เช่น มีข้อมูล collection เลข 1~5 ตามนี้

var collection = new[] { 1, 2, 3, 4, 5 };

AsEnumerable - แปลงให้มันกลับมาเป็น IEnumerable<T> เอาไว้ช่วยแปลงจาก collection อะไรก็ตามให้กลับมาสู่ base class ของกลุ่ม collection

var qry = collection.AsEnumerable();

AsQueryable - แปลงให้คำสั่งทั้งหมดยังเป็นแค่ Query เท่านั้น ซึ่งใช้ได้ดีตอนที่ทำงานร่วมกับ database เพราะเราจะได้ส่งแต่คำสั่งไปประมวลผลที่ database เท่านั้นไม่ได้ส่งข้อมูลปริมาณมหาศาลกลับมาถล่มที่ client

var qry = collection.AsQueryable();

Cast - แปลงข้อมูลจาก data source ให้กลายเป็น data type ที่กำหนด

var collection = new[]
{
    new Dog { Id = 1, OwnerName = "Saladpuk" },
};
IEnumerable<Animal> qry = collection.Cast<Animal>();
public class Animal
{
    public int Id { get; set; }
}
public class Dog : Animal
{
    public string OwnerName { get; set; }
}

OfType - เลือกเอาเฉพาะ data type ที่ตรงกับที่กำหนด

var collection = new object[]
{
    new Dog { OwnerName = "Saladpuk" },
    new Cat { IsFriendly = false },
};
var qry = collection.OfType<Dog>();
// ผลลัพท์: [ { OwnerName = 'Saladpuk' } ]
public class Dog
{
    public string OwnerName { get; set; }
}
public class Cat
{
    public bool IsFriendly { get; set; }
}

ToArray - แปลงให้ collection นั้นๆกลายเป็น Array

var qry = Enumerable.Range(1, 100);
int[] result = qry.ToArray();

ToList - แปลงให้ collection นั้นๆกลายเป็น List

var qry = Enumerable.Range(1, 100);
List<int> result = qry.ToList();

ToDictionary - แปลงให้ collection นั้นๆกลายเป็น Dictionary<K, V> เช่นทำการจัดกลุ่มว่าใครเรียนอยู่ห้องไหนบ้าง แล้วทำการไปสร้างเป็น dictionary

var collection = new[]
{
    new { Id = 1, ClassRoom = "A", Name = "Saladpuk" },
    new { Id = 2, ClassRoom = "B", Name = "Thaksin" },
    new { Id = 3, ClassRoom = "C", Name = "Prayut" },
    new { Id = 4, ClassRoom = "B", Name = "Yingluck" },
    new { Id = 5, ClassRoom = "C", Name = "Abhisit" },
};
var result = collection
    .GroupBy(it => it.ClassRoom)
    .ToDictionary(it => it.Key, it => it);

/* ผลลัพท์
A
{ Id = 1, ClassRoom = A, Name = Saladpuk }
B
{ Id = 2, ClassRoom = B, Name = Thaksin }
{ Id = 4, ClassRoom = B, Name = Yingluck }
C
{ Id = 3, ClassRoom = C, Name = Prayut }
{ Id = 5, ClassRoom = C, Name = Abhisit }
*/

Concatenation Operations

Concate - เป็นการเอา 2 collection มาต่อกันแบบดื้อๆเลย

var a = new[] { 1, 2, 3, 4, 5 };
var b = new[] { 4, 5, 6, 7, 8 };
var qry = a.Concat(b);
// ผลลัพท์: { 1, 2, 3, 4, 5, 4, 5, 6, 7, 8 }

Aggregation Operations

เป็นกลุ่มคำสั่งที่ได้ผลลัพท์กลับมาเลย และเป็นการทำงานแบบ Imperative เช่นมี data source เป็นเลข 1~10 ตามนี้

var collection = Enumerable.Range(1, 10);

Sum - หาผลรวม

var result = collection.Sum();
// ผลลัพท์: 55

Average - หาค่าเฉลี่ย

var result = collection.Average();
// ผลลัพท์: 5.5

Max - หาค่าสูงสุด

var result = collection.Max();
// ผลลัพท์: 10

Min - หาค่าต่ำสุด

var result = collection.Min();
// ผลลัพท์: 1

Count - นับว่าภายใน data source มีข้อมูลอยู่ทั้งหมดเท่าไหร่

var result = collection.Count();
// ผลลัพท์: 10

Aggregate - นำข้อมูลทั้ง collection มาดำเนินการแบบต่อเนื่องกัน

var result = collection.Aggregate((a, b) => a * b);
// ผลลัพท์: 3628800

{% hint style="info" %} แนะนำให้อ่าน
คำสั่ง Aggregate ถ้าเราใช้เป็นจริงๆมันทรงพลังมากเลยนะ ลองศึกษาเพิ่มเติมได้จากลิงค์นี้เบย
Microsoft document - Aggregation {% endhint %}

บทสรุป Deferred vs Imperative

จากคำสั่งทั้งหมดที่เขียนมาเป็นตัวอย่าง สุดท้ายการทำงานของมันก็จะตกมาอยู่ในกลุ่ม 3 กลุ่มนั่นเองคือ

  • ทำงานโดยทันที Immediate
  • ไม่ทำงานจนกว่าจะเรียกใช้ Deferred
    • ดึงข้อมูลทั้งหมดมาก่อนค่อยทำงาน Non-Streaming
    • ค่อยทะยอยดึงข้อมูลมาเรื่อยๆ Streaming
Operators Return Type Immediate Deferred Streaming Deferred Non-Streaming
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Single numeric value X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource X
LongCount Int64 X
Max Single numeric value, TSource, or TResult X
Min Single numeric value, TSource, or TResult X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Single numeric value X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource array X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X