LINQ語法簡介 - 2

by vivid 7. 三月 2018 11:09

.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

若改成擴充方法語法,則寫法如下:

var innerJoin = customers.Join( orders ,
   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」。

Tags:

.NET Magazine國際中文電子雜誌 | C# | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List