LINQ語法簡介 - 1

by vivid 21. 二月 2018 14:36

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:
N180219202
出刊日期: 2018/2/21

撰寫這篇文章的契機是因為最近遇到有些客戶想要學C# 語法,但客戶回饋的訊息是:「因為沒有在用LINQ,所以不想學」。這讓我好生訝異,「LINQ」語法簡單好用,我想客戶是因為對於LINQ不了解才不想用它,否則還有哪個特別的理由會捨棄使用這個語法呢?

LINQ是Language Integrated Query(語言整合查詢)的簡稱,用於存取記憶體中的物件。因為記憶體中物件的資料經常來自於資料庫,例如透過微軟Entity Framework查詢資料,會將查詢出來的資料庫資料轉換成物件,放在集合中,因此感覺上就好像是可以直接查詢資料庫的資料一般。此外LINQ也可以支援XML文件的查詢。

目前微軟的C#與Visual Basic程式語言都支援LINQ語法,如此就可透過一個統一的查詢介面,來查詢不同資料來源的資料,如物件、集合、資料庫、XML甚至服務導向程式。這篇文章將介紹一些常用的語法。

 

LINQ命名空間與類別

「System.Linq」命名空間中包含許多類別與介面支援LINQ查詢,較重要的類別包含「Enumberable」與「Queryable」類別。「Enumberable」類別提供多個靜態方法,用以查詢有實作IEnumerable<T>介面的物件,例如List<T>、Dictionary<T> …等等集合。而「Queryable」類別則用於查詢有實作IQueryable<T>介面的物件,適用於欲查詢的資料型別為未知的情況。例如微軟的Entity Framework便實作了了此介面,提供SQL資料庫資料查詢的功能。

LINQ語法分為兩類:

· 查詢運算式(Query Expression)。

· Lamda擴充方法(Extension Method)。

 

第一個LINQ查詢運算式

LINQ查詢可以在你不熟悉SQL、XQL(XML Query Language)查詢語言的情況下,就可以查詢資料庫或XML文件的內容,我們來看第一個LINQ查詢運算式範例:

using System;
using System.Linq;

namespace LINQDemo {
  class Program {
    static void Main( string [] args ) {
      //指定資料來源
      string [] courses = { "C#" , "Visual Basic" , "LINQ" , "JavaScript" , "ASP.NET" };

      //定義查詢運算式
      var allCourses = from item in courses
                       select item;

      //執行查詢
      foreach ( var item in allCourses ) {
        Console.WriteLine( item );
      }

      Console.ReadLine( );
    }
  }
}


 

在這個範例中,「allCourses」是用來放執行結果的變數。「from」後的「item」代表範圍(Range)變數,「in」後通常接資料序列(Sequence),如IEnumerable或IQueryable類型的集合。「select」則稱為標準查詢運算子(Standard Query Operator)

當這個範例執行,會印出以下結果:

C#
Visual Basic
LINQ
JavaScript
ASP.NET

這個範例使用的語法稱為LINQ 查詢運算式(Query Expression),使用類似SQL的語法來查詢資料,不同的是LINQ查詢語法都是以「from」開始,有別於SQL以「select」開始。要使用LINQ在程式中需要引用「System.Linq」命名空間,預設當你建立C#直專案時便會自動引用。

我們也可以很容易利用「where」查詢運算子來篩選資料,例如以下範例程式碼,將「C」開頭的課程資訊篩選出來:

using System;
using System.Linq;

namespace LINQDemo {
  class Program {
    static void Main( string [] args ) {
      string [] courses = { "C#" , "Visual Basic" , "LINQ" , "JavaScript" , "ASP.NET" };

      var allCourses = from item in courses
                       where item.StartsWith("C")
                       select item;
      foreach ( var item in allCourses ) {
        Console.WriteLine( item );
      }
      Console.ReadLine( );
    }
  }
}


 

這個範例的執行結果如下,印出一個「C#」字串:
C#

 

使用Lamda擴充方法查詢

第二種查詢的方式是透過包含在「Enumerable」或「Queryable」靜態類別中的擴充方法,此種擴充方法又稱Lamda語法,或Fluent語法,例如上例程式可以改寫如下,得到相同的執行結果:

using System;
using System.Linq;

namespace LINQDemo {
  class Program {
    static void Main( string [] args ) {
      string [] courses = { "C#" , "Visual Basic" , "LINQ" , "JavaScript" , "ASP.NET" };

      var allCourses = courses.Where( c => c.Contains( "C" ) );

      foreach ( var item in allCourses ) {
        Console.WriteLine( item );
      }
      Console.ReadLine( );
    }
  }
}

此範例的「Where」是擴充方法,而「c => c.Contains( "C" )」則是Lamda語法。

使用「LINQPad」工具

除了使用微軟的Visual Studio開發工具撰寫LINQ程式之外,還有一個好用的工具:「LINQPad」可以協助LINQ查詢的撰寫,此工具可以從「http://www.linqpad.net/」網站中下載:

clip_image002

圖 1:下載「LINQPad」工具。

舉例來說,從網站下載「LINQPad5Setup.exe」接著執行安裝程式,按「下一步」按鈕進行到下一個步驟,請參考下圖所示:

clip_image004

圖 2:安裝「LINQPad」工具。

選取安裝資料夾,然後按「下一步」按鈕進行到下一個步驟,請參考下圖所示:

clip_image006

圖 3:安裝「LINQPad」工具。

選擇是否建立桌面圖示等設定,然後按「下一步」按鈕進行到下一個步驟,請參考下圖所示:

clip_image008

圖 4:安裝「LINQPad」工具。

按「Next」按鈕,進到下一個步驟,然後按下「Install」按鈕安裝,完成後可以看到以下畫面:

clip_image010

圖 5:安裝「LINQPad」工具。

執行安裝完的LINQPad工具後,我們可以將上述範例直接輸入畫面中的「Query」視窗,然後按下執行按鈕,就可以直接測試程式,請參考下圖所示:

clip_image012

圖 6:在「LINQPad」工具執行LINQ查詢。

 

標準查詢運算子(Standard Query Operator)

LINQ中的標準查詢運算子(Standard Query Operator),實際上是IEnumerable<T>或IQueryable<T>型別的擴充方法。例如前文範例中的「where」與「select」:

 

var allCourses = from item in courses
                 where item.StartsWith("C")
                 select item;

實際上會在編譯階段轉換為擴充方法語法,因此對於查詢效能而言兩種寫法是沒有差別的。

 

Where 查詢運算子

在一個LINQ查詢中,「where」運算子可以出現多次,例如以下範例先使用「where」篩選出「Duration」大於「10」的課程資料,然後再叫用「where」篩選「Programming」類型的課程:

using System;
using System.Linq;

namespace LINQDemo {
  enum ClassType {
    Programming,
    Management,
    Database
  }

  class Course {
    public int CourseId { get; set; }
    public string Title { get; set; }
    public int Duration { get; set; }
    public ClassType Type { get; set; }

  }
  class Program {
    static void Main( string [] args ) {
      Course [] courses = new Course [] {
               new Course () {
                  CourseId = 1 ,
                  Title = "C#" ,
                  Duration = 10,
                  Type  = ClassType.Programming
               },
               new Course () {
                  CourseId = 2 ,
                  Title = "SQL Server" ,
                  Duration = 15,
                  Type  = ClassType.Database
               },
               new Course () {
                  CourseId = 3 ,
                  Title = "Visual Basic" ,
                  Duration = 12,
                  Type  = ClassType.Programming
               },
               new Course () {
                  CourseId = 4 ,
                  Title = "JavaScript" ,
                  Duration = 20,
                  Type  = ClassType.Programming
               },
               new Course () {
                  CourseId = 5 ,
                  Title = "PMP" ,
                  Duration = 20,
                  Type  =ClassType.Management
               }
      };

      var allCourses = from item in courses
                       where item.Duration > 10
                       where item.Type == ClassType.Programming
                       select item;

      foreach ( var item in allCourses ) {
         Console.WriteLine( $" {item.CourseId} - {item.Title} - {item.Duration} - {item.Type}" );
      }
      Console.ReadLine( );
    }
  }
}

 

此範例執行結果印出以下資料:

3 - Visual Basic - 12 - Programming
4 - JavaScript - 20 - Programming

若改用擴充方法語法,程式碼參考如下:

var allCourses = courses.Where( item => item.Duration > 10 && item.Type == ClassType.Programming );

也可以將程式碼撰寫如下,重複叫用兩次「where」來設定多個篩選條件:

var allCourses = courses.Where( item => item.Duration > 10 )
.Where( item => item.Type == ClassType.Programming );

OrderBy 與OrderByDescending查詢運算子

「OrderBy」查詢運算子會將資料由小到大排序;而「OrderByDescending」則是由大到小排序。參考以下範例程式碼,根據「CourseId」做升冪排序:

var allCourses = from item in courses
orderby item.CourseId
select item;

若改用擴充方法語法,程式碼參考如下:

var allCourses = courses.OrderBy( item => item.CourseId );

此範例執行結果印出以下資料:

1 - C# - 10 - Programming
2 - SQL Server - 15 - Database
3 - Visual Basic - 12 - Programming
4 - JavaScript - 20 - Programming
5 - PMP - 20 – Management

參考以下範例程式碼,根據「CourseId」做降冪排序:

var allCourses = from item in courses
orderby item.CourseId descending
select item;

若改用擴充方法語法,程式碼參考如下:

var allCourses = courses.OrderByDescending( item => item.CourseId );

此範例執行結果印出以下資料:

5 - PMP - 20 - Management
4 - JavaScript - 20 - Programming
3 - Visual Basic - 12 - Programming
2 - SQL Server - 15 - Database
1 - C# - 10 - Programming

讓我們試著改寫程式碼如下,若根據Course物件做排序:

var allCourses = from item in courses
orderby item
select item;

這次執行會得到一個例外錯誤訊息,如下:

Unhandled Exception: System.ArgumentException: At least one object must implement IComparable.

錯誤訊息請參考下圖所示:

clip_image014

圖 7:排序例外。

這是因為「Course」類別並未實作「IComparable」介面,無法知道如何比大、小。若利用Visual Studio 做開發,可以透過工具的輔助來實做介面。先修改「Course」類別程式碼,使其實作「IComparable」介面,然後將滑鼠移動到「IComparable」介面上方,此時Visual Studio會自動出現一個燈泡圖示(Quick Acton),詢問要如何實作介面,請參考下圖所示:

clip_image016

圖 8:實作「IComparable」介面。

我們希望根據「CourseId」的值來排序,因此選取上圖的「Implement interface through ‘CourseId’」項目,此時Visual Studio工具將自動產生「CompareTo」方法程式碼如下:

class Course : IComparable {
  public int CourseId { get; set; }
  public string Title { get; set; }
  public int Duration { get; set; }
  public ClassType Type { get; set; }

  public int CompareTo( object obj ) {
    return CourseId.CompareTo( obj );
  }
}

修改「CompareTo」方法方法程式碼如下:

class Course : IComparable {
  public int CourseId { get; set; }
  public string Title { get; set; }
  public int Duration { get; set; }
  public ClassType Type { get; set; }

  public int CompareTo( object obj ) {
    return CourseId.CompareTo( ( (Course) obj ).CourseId );
  }
}

這樣才能順利執行查詢。

 

多屬性欄位排序與ThenBy與ThenByDecending

若要根據多個屬性欄位做排序,可以在「orderBy」 運算子後方使用「,」號區隔屬性欄位,例如以下範例程式碼先根據「Duration」再根據「CourseId」進行降冪排序:

var allCourses = from item in courses
orderby item.Duration, item.CourseId descending
select item;

若要在擴充方法查詢根據兩個以上的屬性欄位做排序,可以叫用「ThenBy」或「ThenByDecending」擴充方法,上例程式可以修改如下:

var allCourses = courses.OrderBy( item => item.Duration ).ThenByDescending( item => item.CourseId );

此範例執行結果印出以下資料:

1 - C# - 10 - Programming
3 - Visual Basic - 12 - Programming
2 - SQL Server - 15 - Database
5 - PMP - 20 - Management
4 - JavaScript - 20 – Programming

以下範例程式碼先根據「Duration」再根據「CourseId」進行升冪排序

var allCourses = from item in courses
orderby item.Duration, item.CourseId ascending
select item;

若改用擴充方法語法,程式碼參考如下:

var allCourses = courses.OrderBy( item => item.Duration ).ThenBy( item => item.CourseId );

此範例執行結果印出以下資料:

1 - C# - 10 - Programming
3 - Visual Basic - 12 - Programming
2 - SQL Server - 15 - Database
4 - JavaScript - 20 - Programming
5 - PMP - 20 – Management

 

GroupBy查詢運算子

「GroupBy」查詢運算子可以根據指定的「key」值將物件作分組。分組資料將放在實作IGrouping<TKey,TSource>介面的物件中,TKey表示「key」值;TSource則是滿足分組條件的物件。以下程式碼根據課程的類型(Type)作分組,每一個分組中包含一個集合存放此隸屬於此群組的Course資料:

var allGroups = from item in courses
                group item by item.Type;

foreach ( var group in allGroups ) {
  Console.WriteLine( $" Group : {group.Key} " );
  foreach ( var c in group ) {
    Console.WriteLine( $" \t {c.CourseId} - {c.Title} - {c.Duration} - {c.Type}" );
  }
}

若改用擴充方法語法,程式碼參考如下:

var allGroups = courses.GroupBy( item => item.Type );

這個範例程式的執行結果參考如下:

Group : Programming
1 - C# - 10 - Programming
3 - Visual Basic - 12 - Programming
4 - JavaScript - 20 – - Programming
Group : Database
2 - SQL Server - 15 - Database
Group : Management
5 - PMP - 20 – Management

Tags:

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

新增評論




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






NET Magazine國際中文電子雜誌

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

月分類Month List