Day 18 我們自己建立了 FindOrders():
FindOrders(
orders,
order => order.Quantity > 10
);
由 FindOrders() 負責逐筆處理資料,再由 Lambda 決定篩選條件。
但實際開發中,以下需求非常常見:
篩選資料
轉換資料
排序資料
判斷資料是否存在
取得第一筆符合條件的資料
如果每次都重新撰寫 foreach 與處理流程,很容易產生重複程式。
因此 C# 提供了:
LINQ
例如 Day 18 的:
FindOrders(
orders,
order => order.Quantity > 10
);
使用 LINQ 可以改成:
orders.Where(
order => order.Quantity > 10
);
今天就從這裡正式進入 LINQ。
Where()
→ 篩選
Select()
→ 轉換
Any()
→ 判斷是否存在
FirstOrDefault()
→ 取得第一筆
OrderBy()
→ 排序
ToList()
→ 建立 List
今天最重要的觀念:
LINQ 負責資料操作,Lambda 負責操作規則。
LINQ 全名:
Language Integrated Query
中文通常稱為:
語言整合查詢
初學可以先理解成:
LINQ 是 C# 提供的一套資料查詢與處理方式。
例如:
orders.Where(
order => order.Quantity > 10
);
這段程式可以拆成兩個角色:
Where()
→ LINQ Method
→ 負責篩選
order => order.Quantity > 10
則是 Day 18 學過的 Lambda:
Lambda
→ 提供篩選規則
所以可以這樣理解:
LINQ
→ 要對資料做什麼?
Lambda
→ 要使用什麼規則?
今天都使用同一組 Order:
using System.Linq;
List<Order> orders =
new List<Order>
{
new Order("A001", "TXF", 5, 1500m),
new Order("A002", "MXF", 20, 800m),
new Order("A003", "TXF", 15, 1600m),
new Order("A004", "EXF", 8, 1200m),
new Order("A005", "TXF", 25, 1700m)
};
class Order
{
public string OrderId { get; }
public string Product { get; }
public int Quantity { get; }
public decimal Price { get; }
public Order(
string orderId,
string product,
int quantity,
decimal price)
{
OrderId = orderId;
Product = product;
Quantity = quantity;
Price = price;
}
}
資料如下:
| OrderId | Product | Quantity | Price |
|---|---|---|---|
| A001 | TXF | 5 | 1500 |
| A002 | MXF | 20 | 800 |
| A003 | TXF | 15 | 1600 |
| A004 | EXF | 8 | 1200 |
| A005 | TXF | 25 | 1700 |
Where():篩選資料需求:
找出 Quantity 大於 10 的 Order。
可以使用:
List<Order> result =
orders
.Where(
order => order.Quantity > 10
)
.ToList();
Where() 的用途是:
逐筆判斷資料,只保留條件為
true的元素。
這裡的:
order => order.Quantity > 10
代表:
收到 Order
↓
判斷 Quantity > 10
↓
true / false
實際結果:
| Order | 判斷 | 結果 |
|---|---|---|
| A001 / 5 | 5 > 10 |
排除 |
| A002 / 20 | 20 > 10 |
保留 |
| A003 / 15 | 15 > 10 |
保留 |
| A004 / 8 | 8 > 10 |
排除 |
| A005 / 25 | 25 > 10 |
保留 |
最後取得:
A002
A003
A005
如果條件改成:
Product == "TXF"
只需要更換 Lambda:
List<Order> txfOrders =
orders
.Where(
order => order.Product == "TXF"
)
.ToList();
所以:
Where()
→ 負責篩選
Lambda
→ 負責篩選條件
另外,Where() 不會把不符合條件的資料從原本的 orders 刪掉,而是產生符合條件的查詢結果。
Select():轉換資料如果需求不是「哪些資料要留下」,而是:
只取得每筆 Order 的 Product。
這時使用:
Select()
例如:
List<string> products =
orders
.Select(
order => order.Product
)
.ToList();
結果:
TXF
MXF
TXF
EXF
TXF
這裡:
order => order.Product
代表:
收到 Order
↓
取得 Product
↓
回傳 string
所以資料從:
Order
轉換成:
string
Select() 的核心就是:
把每一筆來源資料轉換成需要的結果。
Where() 和 Select() 的差別這兩個是 LINQ 最重要的基礎 Method。
orders.Where(
order => order.Quantity > 10
);
Where() 的 Lambda 回傳 bool,是在判斷:
這筆資料要不要留下?
而:
orders.Select(
order => order.Product
);
Select() 則是在決定:
這筆資料要變成什麼?
整理:
| Method | 核心問題 | 用途 |
|---|---|---|
Where() |
要不要這筆? | 篩選 |
Select() |
要變成什麼? | 轉換 |
一句話記住:
Where()決定哪些資料留下;Select()決定資料要變成什麼。
Any():有沒有符合條件的資料?有時候不需要取得所有符合條件的資料,只想知道:
有沒有 Quantity 大於 10 的 Order?
可以:
bool hasLargeOrder =
orders.Any(
order => order.Quantity > 10
);
結果:
true
如果:
bool hasLargeOrder =
orders.Any(
order => order.Quantity > 100
);
結果:
false
所以:
Any()
→ 只回答「有沒有?」
有符合資料
→ true
沒有
→ false
和 Where() 的差別:
Where()
→ 我要符合條件的資料
Any()
→ 我只想知道有沒有
FirstOrDefault():取得第一筆需求:
找出第一筆 Product 是 TXF 的 Order。
可以:
Order? order =
orders.FirstOrDefault(
order => order.Product == "TXF"
);
結果是:
A001
如果找不到:
Order? order =
orders.FirstOrDefault(
order => order.Product == "ABC"
);
在這個 Order 範例中會得到:
null
因此可以:
if (order is not null)
{
Console.WriteLine(order.OrderId);
}
else
{
Console.WriteLine("找不到訂單");
}
先記住:
FirstOrDefault()
→ 第一筆符合條件的資料
找到
→ 回傳那筆資料
找不到
→ 回傳 Default Value
目前 Order 是 Reference Type,因此這裡找不到時是 null。
OrderBy():排序資料需求:
依 Quantity 從小到大排序。
可以:
List<Order> sortedOrders =
orders
.OrderBy(
order => order.Quantity
)
.ToList();
結果:
A001 → 5
A004 → 8
A003 → 15
A002 → 20
A005 → 25
這裡:
OrderBy()
→ 執行排序
order.Quantity
→ 指定排序依據
如果要由大到小:
List<Order> sortedOrders =
orders
.OrderByDescending(
order => order.Quantity
)
.ToList();
所以:
OrderBy()
→ 小到大
OrderByDescending()
→ 大到小
前面都是單獨使用 LINQ Method。
實際開發中,更常見的是把多個操作組合起來。
需求:
找出 Product 是 TXF,而且 Quantity 大於 10 的 Order,依 Quantity 由大到小排序,最後只取得 OrderId。
可以:
List<string> result =
orders
.Where(
order => order.Product == "TXF"
)
.Where(
order => order.Quantity > 10
)
.OrderByDescending(
order => order.Quantity
)
.Select(
order => order.OrderId
)
.ToList();
從上往下閱讀:
orders
↓
Where()
只保留 Product == "TXF"
↓
Where()
只保留 Quantity > 10
↓
OrderByDescending()
依 Quantity 由大到小
↓
Select()
轉成 OrderId
↓
ToList()
建立 List<string>
最後結果:
A005
A003
這種將多個 Method 串在一起的寫法稱為:
Method Chaining
也就是:
把多個資料處理操作組合成一個查詢流程。
這也是 LINQ 很重要的使用方式。
ToList():建立 List前面的範例常看到:
.ToList();
例如:
List<Order> result =
orders
.Where(
order => order.Quantity > 10
)
.ToList();
初學可以先理解:
如果最後需要一個
List<T>,可以使用ToList()將查詢結果建立成 List。
例如:
Where(...)
↓
查詢結果
↓
ToList()
↓
List<Order>
不是所有 LINQ Method 都需要 ToList()。
例如:
bool exists =
orders.Any(
order => order.Quantity > 10
);
Any() 已經直接得到:
bool
而:
Order? order =
orders.FirstOrDefault(
order => order.Product == "TXF"
);
FirstOrDefault() 已經直接取得一筆資料或 null。
補充:
Where()、Select()等部分 LINQ 查詢具有 Deferred Execution(延遲執行)的特性
學 LINQ 不需要一開始背很多 API。
先問自己:
我現在想對資料做什麼?
| 需求 | LINQ Method |
|---|---|
| 哪些資料符合? | Where() |
| 資料要變成什麼? | Select() |
| 有沒有符合的資料? | Any() |
| 第一筆符合的是誰? | FirstOrDefault() |
| 怎麼排序? | OrderBy() / OrderByDescending() |
| 最後需要 List? | ToList() |
可以簡單記成:
哪些?
→ Where()
變什麼?
→ Select()
有沒有?
→ Any()
第一筆?
→ FirstOrDefault()
怎麼排?
→ OrderBy()
要 List?
→ ToList()
重點不是背 Method Name。
而是:
先理解需求,再選擇適合的 LINQ Method。
今天從 Day 18 自己建立:
FindOrders(
orders,
order => order.Quantity > 10
);
進一步走到 LINQ:
orders.Where(
order => order.Quantity > 10
);
核心關係是:
LINQ
→ 負責資料操作
Lambda
→ 負責操作規則
常用 Method:
Where()
→ 篩選
Select()
→ 轉換
Any()
→ 有沒有
FirstOrDefault()
→ 第一筆
OrderBy()
→ 排序
ToList()
→ 建立 List
最後記住兩件事:
先理解需求,再選擇 LINQ Method。
LINQ 負責「對資料做什麼」,Lambda 負責「使用什麼規則」。