.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N180319301
出刊日期: 2018/3/7
在這篇文章中,將延續《LINQ語法簡介 - 1》一文的情境,介紹常用的LINQ運算子(Operator),以透過更簡易的語法來查詢陣列或集合中的內容。
Select運算子
LINQ查詢運算式的語法,通常以「select」或「groupby」關鍵字結束,「select」運算子會回傳IEnumerable<T>集合,集合中的項目包含的值則來自於轉換程式。以下程式碼範例利用「select」運算子回傳「customers」集合中「Customer」物件的「CustomerName」屬性值:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
class Customer {
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public string ContactName { get; set; }
public string City { get; set; }
public int PostalCode { get; set; }
public string Country { get; set; }
public int CustomerTypeId { get; set; }
}
class Progarm {
static void Main( string [] args ) {
List<Customer> customers = new List<Customer> {
new Customer(){ CustomerID = 1, CustomerTypeId = 1 , CustomerName ="Mary " , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
new Customer(){ CustomerID = 2, CustomerTypeId = 1 , CustomerName ="Ana " , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
new Customer(){ CustomerID = 3, CustomerTypeId = 2 , CustomerName ="Lili" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "UK" },
new Customer(){ CustomerID = 4, CustomerTypeId = 3 , CustomerName ="Betty" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "US" }
};
var result = from c in customers
select c.CustomerName;
foreach ( var item in result ) {
Console.WriteLine( item );
}
}
}
}
此範例執行結果參考如下:
Mary
Ana
Lili
Betty
若改成擴充方法語法,則寫法如下:
var result = customers.Select( c => c.CustomerName );
回傳匿名型別
而以下範例程式則利用「select」運算子回傳一個匿名型別所成的集合,此匿名型別包含「CustomerID」、「CustomerName」與「ContactInfo」三個屬性,而「ContactInfo」屬性值是由Customer物件的「ContactName」、「City」與「Country」三個屬性組成:
var result = from c in customers
select new {
c.CustomerID ,
c.CustomerName,
ContactInfo = $"{ c.ContactName} - {c.City} - {c.Country}"
};
foreach ( var item in result ) {
Console.WriteLine( $" {item.CustomerID} , {item.CustomerName} , {item.ContactInfo}" );
}
這個範例執行結果參考如下:
1 , Mary , Maria Anders - Berlin - Germany
2 , Ana , Ana Trujillo - Mexico - Mexico
3 , Lili , Futterkiste - Mexico - UK
4 , Betty , Futterkiste - Mexico - US
若改成擴充方法語法,則寫法如下:
var result = customers.Select( c => new {
c.CustomerID ,
c.CustomerName ,
ContactInfo = $"{ c.ContactName} - {c.City} - {c.Country}"
} );
SelectMany運算子
有時在查詢運算式中我們可能會使用到兩個以上的「from」運算子,例如以下範例程式碼,第一個「from」從「customers」集合中找出所有的顧客(Customer),而第二個「from」則從每一個「Customer」物件的「Orders」屬性找出所有的訂單(Order)資料,篩選出「EmployeeID」為「2」的訂單資料後,並回傳這個「Order」物件:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
class Order {
public int OrderID { get; set; }
public int CustomerID { get; set; }
public int EmployeeID { get; set; }
public DateTime OrderDate { get; set; }
}
class Customer {
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public List<Order> Orders { get; set; }
}
class Progarm {
static void Main( string [] args ) {
List<Customer> customers = new List<Customer> {
new Customer(){ CustomerID = 1, CustomerName ="Mary " ,
Orders = new List<Order> {
new Order(){ OrderID = 10001 , EmployeeID = 1 , OrderDate = new DateTime(2018,9,18) },
new Order(){ OrderID = 10002 , EmployeeID = 2 , OrderDate = new DateTime(2018,9,19) }
}
},
new Customer(){ CustomerID = 2, CustomerName ="Ana " ,
Orders = new List<Order> {
new Order(){ OrderID = 10003 , EmployeeID = 2, OrderDate = new DateTime( 2018 , 9 , 20 ) } ,
new Order(){ OrderID = 10004 , EmployeeID = 3 , OrderDate = new DateTime( 2018 , 9 , 20 )}
}
} };
var result = from c in customers
from o in c.Orders
where o.EmployeeID == 2
select o;
foreach ( var item in result ) {
Console.WriteLine( item.OrderID );
}
}
}
}
此範例執行結果參考如下:
10002
10003
若改成擴充方法語法,則寫法如下,使用SelectMany運算子:
var result = customers.SelectMany( c => c.Orders ).Where( o => o.EmployeeID == 2 );
若除了查出訂單編號之外,還想要得到顧客的名稱、以及訂單的日期等資訊,此時便可以使用以下程式碼,回傳一個匿名型別:
var result = from c in customers
from o in c.Orders
where o.EmployeeID == 2
select new { o.OrderID , c.CustomerName , o.OrderDate , o.EmployeeID };
foreach ( var item in result ) {
Console.WriteLine( $"{ item.OrderID} - {item.CustomerName} - {item.OrderDate.ToShortDateString()}" );
}
此範例執行結果參考如下:
10002 - Mary - 9/19/2018
10003 - Ana - 9/20/2018
若改成擴充方法語法,則寫法如下:
var result = customers.SelectMany( c => c.Orders ,
( c , o ) => new { o.OrderID , c.CustomerName , o.OrderDate , o.EmployeeID } )
.Where( o => o.EmployeeID == 2 );
內部連接Join運算子
LINQ包含類似SQL的內部連接(Inner Join)語法,可以將兩個不同集合(或稱序列)中,鍵值(key)相符的項目找出並回傳。
參考以下範例程式碼,「Customer」代表顧客資料,「Order」則描述訂單資料,一個「Customer」物件可能會有零到多個相關的「Order」物件。範例一開始建立「customers」、「orders」兩個集合,「join」運算子找出外部序列(customers集合)與內部序列(orders集合)中「CustomerID」欄位值相符的資料找出:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
class Order {
public int OrderID { get; set; }
public int CustomerID { get; set; }
public int EmployeeID { get; set; }
public DateTime OrderDate { get; set; }
public int ShipperID { get; set; }
}
class Customer {
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public string ContactName { get; set; }
public string City { get; set; }
public int PostalCode { get; set; }
public string Country { get; set; }
}
class Program {
static void Main( string [] args ) {
List<Customer> customers = new List<Customer> {
new Customer(){ CustomerID = 1, CustomerName ="Mary " , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
new Customer(){ CustomerID = 2, CustomerName ="Ana " , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
new Customer(){ CustomerID = 3, CustomerName ="Lili" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "UK" },
new Customer(){ CustomerID = 4, CustomerName ="Betty" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "US" }
};
List<Order> orders = new List<Order> {
new Order(){ OrderID = 10001 , CustomerID = 2 , EmployeeID = 7 , OrderDate = new DateTime(2018,9,18) ,ShipperID = 3 },
new Order(){ OrderID = 10002 , CustomerID = 2 , EmployeeID = 3 , OrderDate = new DateTime(2018,9,19) ,ShipperID = 1},
new Order(){ OrderID = 10003 , CustomerID = 3 , EmployeeID = 8 , OrderDate = new DateTime(2018,9,20) ,ShipperID = 2 }
};
var innerJoin = from c in customers // 外部序列 (集合)
join o in orders //內部序列 (集合)
on c.CustomerID equals o.CustomerID //外部key equaqls 內部key
select new {
CustomerID = c.CustomerID ,
CustomerName = c.CustomerName ,
EmployeeID = o.EmployeeID ,
ShipperID = o.ShipperID ,
OrderDate = o.OrderDate
};
foreach ( var item in innerJoin ) {
Console.WriteLine( $@" {item.CustomerID} - {item.CustomerName}
- EmployeeID : {item.EmployeeID}
- ShipperID : {item.ShipperID}
- OrderDate :{item.OrderDate.ToShortDateString( )}" );
}
}
}
}
範例中「from.. in」後頭接外部序列(集合);「join in」後頭接內部序列(集合),「on」關鍵字後頭用來設定(Key Selector),語法是「外部key equaqls 內部key」,不可以使用C#「==」運算子來比對。這個範例的執行的結果請參考下列所示:
2 - Ana
- EmployeeID : 7
- ShipperID : 3
- OrderDate :9/18/2018
2 - Ana
- EmployeeID : 3
- ShipperID : 1
- OrderDate :9/19/2018
3 - Lili
- EmployeeID : 8
- ShipperID : 2
- OrderDate :9/20/2018
若改成擴充方法語法,則寫法如下:
c => c.CustomerID ,
o => o.CustomerID ,
( c , o ) => new {
CustomerID = c.CustomerID ,
CustomerName = c.CustomerName ,
EmployeeID = o.EmployeeID ,
ShipperID = o.ShipperID ,
OrderDate = o.OrderDate
}
);
同樣的「customers」為外部序列(集合);「orders」為內部序列(集合)。「join」方法的第一個參數是內部序列;第二個參數是「外部Key Selector」;第三個參數是「內部Key Selector」;最後一個參數是「Result Selector」。
分組連接 - GroupJoin運算子
「GroupJoin」運算子和「join」運算子非常類似,執行連接查詢,和「join」運算子不同的地方在於「GroupJoin」運算子會根據特定的群組鍵值(Group Key)回傳群組資料。「GroupJoin」運算子可以將兩個不同集合(或稱序列)中,鍵值(key)相符的項目找出並回傳分組資料與鍵值。
參考以下範例程式碼,「CustomerType」類別用來描述顧客(Customer)的類型,「Customer」類別則包含一個「CustomerTypeId」的屬性對應到「CustomerType」類別:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
class CustomerType {
public int CustomerTypeId { get; set; }
public string CustomerTypeName { get; set; }
public string Note { get; set; }
}
class Customer {
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public string ContactName { get; set; }
public string City { get; set; }
public int PostalCode { get; set; }
public string Country { get; set; }
public int CustomerTypeId { get; set; }
}
class Progarm {
static void Main( string [] args ) {
List<CustomerType> customerTypes = new List<CustomerType> {
new CustomerType(){ CustomerTypeId = 0 , CustomerTypeName = "Golden" },
new CustomerType(){ CustomerTypeId = 1 , CustomerTypeName = "Newbie" },
new CustomerType(){ CustomerTypeId = 2 , CustomerTypeName = "Junior"},
new CustomerType(){ CustomerTypeId = 3 , CustomerTypeName = "Senior" }
};
List<Customer> customers = new List<Customer> {
new Customer(){ CustomerID = 1, CustomerTypeId = 1 , CustomerName ="Mary " , ContactName = "Maria Anders" , City = "Berlin", PostalCode = 12209 , Country = "Germany" },
new Customer(){ CustomerID = 2, CustomerTypeId = 1 , CustomerName ="Ana " , ContactName = "Ana Trujillo" , City = "México ", PostalCode = 05021 , Country = "Mexico" },
new Customer(){ CustomerID = 3, CustomerTypeId = 2 , CustomerName ="Lili" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "UK" },
new Customer(){ CustomerID = 4, CustomerTypeId = 3 , CustomerName ="Betty" , ContactName="Futterkiste" , City = "México ", PostalCode = 05023 , Country = "US" }
};
var groups = from t in customerTypes // 外部序列 (集合)
join c in customers // 內部序列 (集合)
on t.CustomerTypeId equals c.CustomerTypeId into customerGroups
select new { // result selector
CustomerGroups = customerGroups ,
CustomerTypeId = t.CustomerTypeId ,
CustomerTypeName = t.CustomerTypeName
};
foreach ( var g in groups ) {
Console.WriteLine( $"Customer Types: {g.CustomerTypeId} - {g.CustomerTypeName}" );
foreach ( var item in g.CustomerGroups ) {
Console.WriteLine( $"\t Customer : {item.CustomerID} - {item.CustomerName} - {item.Country} " );
}
}
}
}
}
範例中「from .. in」後頭接外部序列(集合);「join .. in」後頭接內部序列(集合),「on」關鍵字後頭用來設定Key Selector,語法是「外部key equaqls 內部key」,不可以使用C#「==」運算子來比對。最後使用「into」建立分組集合,這個範例的執行的結果請參考下列所示:
Customer Types: 0 - Golden
Customer Types: 1 - Newbie
Customer : 1 - Mary - Germany
Customer : 2 - Ana - Mexico
Customer Types: 2 - Junior
Customer : 3 - Lili - UK
Customer Types: 3 - Senior
Customer : 4 - Betty – US
若改成擴充方法語法,則寫法如下:
var groups = customerTypes.GroupJoin( customers ,
t => t.CustomerTypeId ,
c => c.CustomerTypeId ,
( t , customerGroups ) => new {
CustomerGroups = customerGroups ,
CustomerTypeName = t.CustomerTypeName ,
CustomerTypeId = t.CustomerTypeId
} );
此範例中「customerTypes」為外部序列(集合),「GroupJoin」方法的第一個參數「customers」為內部序列(集合),第二個參數是「外部Key Selector」;第三個參數是「內部Key Selector」;最後一個參數是「Result Selector」。
Quantifier 運算子 - All
LINQ查詢運算式語法目前不支援All運算子。你可以叫用All方法檢查集合中的項目是否全部滿足條件,若是,則All方法將回傳「True」;否則則回傳「False」。參考以下範例程式碼,檢查所有「CustomerID」為「2」,且訂單(Order)的月份是否為「9」月:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
class Order {
public int OrderID { get; set; }
public int CustomerID { get; set; }
public int EmployeeID { get; set; }
public DateTime OrderDate { get; set; }
public int ShipperID { get; set; }
}
class Program {
static void Main( string [] args ) {
List<Order> orders = new List<Order> {
new Order(){ OrderID = 10001 , CustomerID = 2 , EmployeeID = 7 , OrderDate = new DateTime(2018,9,18) ,ShipperID = 3 },
new Order(){ OrderID = 10002 , CustomerID = 2 , EmployeeID = 3 , OrderDate = new DateTime(2018,9,19) ,ShipperID = 1},
new Order(){ OrderID = 10003 , CustomerID = 3 , EmployeeID = 8 , OrderDate = new DateTime(2018,9,20) ,ShipperID = 2 }
};
var result = orders.All( o => o.CustomerID == 2 && o.OrderDate.Month == 9 );
Console.WriteLine( result);
}
}
}
當然此執行結果為「False」;若修改所有 「Order」物件的「CustomerID」屬性值為「2」,執行結果就會回傳「True」:
List<Order> orders = new List<Order> {
new Order(){ OrderID = 10001 , CustomerID = 2 , EmployeeID = 7 , OrderDate = new DateTime(2018,9,18) ,ShipperID = 3 },
new Order(){ OrderID = 10002 , CustomerID = 2 , EmployeeID = 3 , OrderDate = new DateTime(2018,9,19) ,ShipperID = 1},
new Order(){ OrderID = 10003 , CustomerID = 2 , EmployeeID = 8 , OrderDate = new DateTime(2018,9,20) ,ShipperID = 2 }
};
var result = orders.All( o => o.CustomerID == 2 && o.OrderDate.Month == 9 );
Console.WriteLine( result);
Quantifier 運算子 - Any
「Any」運算子檢查集合中的項目只要有一個項目滿足條件,則「Any」方法將回傳「True」;若所有項目都不滿足條件才會回傳「False」。LINQ查詢運算式語法目前不支援「Any」運算子。參考以下範例程式碼,檢查是否有「CustomerID」為「2」,且訂單(Order)的月份是「9」月的訂單資料:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LINQDemo {
class Order {
public int OrderID { get; set; }
public int CustomerID { get; set; }
public int EmployeeID { get; set; }
public DateTime OrderDate { get; set; }
public int ShipperID { get; set; }
}
class Program {
static void Main( string [] args ) {
List<Order> orders = new List<Order> {
new Order(){ OrderID = 10001 , CustomerID = 2 , EmployeeID = 7 , OrderDate = new DateTime(2018,9,18) ,ShipperID = 3 },
new Order(){ OrderID = 10002 , CustomerID = 2 , EmployeeID = 3 , OrderDate = new DateTime(2018,9,19) ,ShipperID = 1},
new Order(){ OrderID = 10003 , CustomerID = 3 , EmployeeID = 8 , OrderDate = new DateTime(2018,9,20) ,ShipperID = 2 }
};
var result = orders.Any( o => o.CustomerID == 2 && o.OrderDate.Month == 9 );
Console.WriteLine( result);
}
}
}
這個範例的結果將會是「true」。