語言集成查詢

語言集成查詢（Language Integrated Query，縮寫：LINQ），發音"link"，是微軟的一項技術，新增一種自然查詢的SQL語法到.NET Framework的程式語言中，目前可支援C#以及Visual Basic .NET語言。2007年11月19日隨.NET Framework 3.5發布了LINQ技術。

包括LINQ to Objects、LINQ to SQL、LINQ to Datasets、LINQ to Entities、LINQ to Data Source、LINQ to XML/XSD等。

## 語言風格

LINQ依賴於語言的多項新增風格，來展示出查詢語言的擴充性。例如：C#:

### 匿名型別

匿名型別（Anonymous type）是C# 3.0與Visual Basic 9.0新增的功能，它允許開發人員可以使用不具型別的方式建立新的資料結構，而真正的型別在編譯時期，由C# (或VB) Compiler自動產生，並寫入編譯目的檔中，它可以讓開發人員能夠很簡單利用匿名型別建立物件，LINQ中的select指令即是利用這種特性來建立回傳物件。

匿名類型本質上是表達元組（tuple），採用值語義。

下列使用匿名型別的程式碼：

```csharp
    [WebGet]
    public IQueryable<Categories> GetCategoryByName(string CategoryName)
    {
        try
        {
            var query = base.CurrentDataSource.Categories.Where
　　　　　　　　　("it.CategoryName = @Name", new ObjectParameter[] { new ObjectParameter("Name", CategoryName) });
        }
        catch (Exception)
        {
            throw;
        }
        return query;
    } 
```

會由編譯器改寫為：

```csharp
    [WebGet]
    public IQueryable<Categories> GetCategoryByName(string CategoryName)
    {
        IQueryable<Categories> CS$1$0000;　// 由編譯器改寫而成。
        try
        {
            CS$1$0000 = base.CurrentDataSource.Categories.Where
　　　　　　　　　("it.CategoryName = @Name", new ObjectParameter[] { new ObjectParameter("Name", CategoryName) });
        }
        catch (Exception)
        {
            throw;
        }
        return CS$1$0000;
    }
```

### 查詢表達式語法

```
from RangeVariable in IEnumerable<T>或IQueryable<T>的Collection
<Standard Query  Operators> <lambda expression>
<select or groupBy operator> <result   formation>
```

### 流利語法

LINQ查詢時有兩種語法可供選擇：查詢表達式語法（Query Expression）和流利語法（Fluent Syntax）。前者使用查詢運算符；後者利用System.Linq.Enumerable類中定義的擴展方法和Lambda表達式方式進行查詢。CLR本身並不理解查詢表達式語法，它只理解流利語法。編譯器負責把查詢表達式語法編譯為流利語法。

以下是一個示例LINQ方法語法的查詢，返回陣列中的偶數：

```csharp
int[] ints={1,2,3,4};
var result = ints.Where(p => p % 2 == 0).ToArray();
```

對比流利語法和C#的傳統語法：

```csharp
//   extension methods make LINQ elegant
            IEnumerable<string> query = names
                .Where(n => n.Contains("a"))
                .OrderBy(n => n.Length)
                .Select(n => n.ToUpper());
 
//   static methods lose query's fluency
            IEnumerable<string> query2 =
                Enumerable.Select(
                    Enumerable.OrderBy(
                        Enumerable.Where(names, n => n.Contains("a")
                        ), n => n.Length
                    ), n => n.ToUpper()
                );
```

### 標準查詢運算子 (Standard query operators)

System.Linq.Enumerable靜態類聲明了一套標準查詢操作符（Standard Query Operators，SQO）方法集合。基本語法如下：

```csharp
 using (var db = new EntityContext())
 {
     var roles = from o in db.Users
                where o.Account == "Apollo"
                select o.Roles;
     …
 }
```

標準查詢操作符和Lambda表達式的關係非常密切。編譯器會將上述表達式轉化為下述以Lambda表達式為參數的顯式擴展方法調用序列：

```csharp
 using (var db = new EntityContext())
 {
     var roles = db.Users.Where(o => o.Account == "Apollo").Select(o => o.Roles);
 }
```

操作符｜類別｜語義｜流利語法示例｜查詢表達式語法示例

Where｜篩選操作符(Restriction)｜Predicate→bool

```csharp
var user = 
db.Users.Where(
o => o.Roles != null);
```

```csharp
 
var users = 
from o in db.Users
where o.Roles != null
select o;
```

Select｜投影操作符(Projection)｜將對象投影為一個匿名類型實例TSource→TResult

```csharp
var users = 
db.Users.Select
(o => new { o.Account, o.Password });
```

```csharp
var users = 
from o in db.Users
select new { o.Account, o.Password };
```

SelectMany｜投影操作符(Projection)｜返回多行結果，用於多表的交叉連接（cross join）

```vb
Dim res = Employees.SelectMany(Function(e)
  e.Family.Select(Function(c)c.name)) 
```

Skip｜分塊操作符(Partitioning)｜跳過前n個元素

```csharp
var users = 
db.Users.OrderBy(
o => o.Roles.Count
).Skip(10);
```

SkipWhile｜分塊操作符(Partitioning)｜跳過起始處使條件為真的所有元素

```csharp
var users = 
db.Users.OrderBy(
o => o.Roles.Count).SkipWhile(
o => o.Roles == 3);
```

Take｜分塊操作符(Partitioning)｜返回開頭之處的n個元素

```csharp
var users = 
db.Users.OrderBy(
o => o.Roles.Count).Take(5);
```

TakeWhile｜分塊操作符(Partitioning)｜返回起始處使條件為真的所有元素

```csharp
var users = 
db.Users.OrderBy(
o => o.Roles.Count).TakeWhile(
o => o.Roles.Count == 3);
```

Join｜連接操作符｜內連接兩個或多個表，僅限於Equals運算符

```csharp
var categoriesProducts = 
from c in nWEntities.Categories
join p in nWEntities.Products
on c.CategoryID equals p.CategoryID
into productsByCategoryID
select new
{
c.CategoryName,
productCount = 
productsByCategoryID.Count()
};
```

GroupJoin｜連接操作符｜類似於LEFT OUTER JOIN，右側集合匹配於左側集合鍵值的元素被分組

```vb
From cust In customers Group 
Join ord In orders 
On cust.CustomerID 
 Equals ord.CustomerID 
Into CustomerOrders = Group, 
   OrderTotal = Sum(ord.Total)
```

Concat｜合併操作符｜用於連接兩個序列

OrderBy｜排序操作符(Ordering)｜升序排列TSource→TKey

```csharp
var users = 
db.Users.OrderBy(
o => o.Roles.Count);
```

```csharp
var users = 
from o in db.Users
orderby o.Roles.Count
select o;
```

OrderByDescending｜排序操作符(Ordering)｜降序排列

```csharp
var users = 
db.Users.OrderByDescending(
o => o.Roles.Count);
```

```csharp
var users = 
from o in db.Users
orderby o.Roles.Count descending
select o;
```

ThenBy｜排序操作符(Ordering)｜只能對IOrderedEnumerable接口對象使用

ThenByDescending｜排序操作符(Ordering)｜只能對IOrderedEnumerable接口對象使用

Reverse｜排序操作符(Ordering)｜只能對IOrderedEnumerable接口對象使用

GroupBy｜分組操作符

```csharp
var users = 
db.Users.GroupBy(
o => o.Roles.Count);
```

```csharp
var users = 
from o in db.Users
group o by o.Roles.Count into g
select new { RoleCount = g.Key, Group = g };
```

Distinct｜集合操作符｜去重複

```csharp
var roles = 
user.Roles.Distinct();
```

Union｜集合操作符｜集合併，還去重複

```csharp
var roles = 
user1.Roles.Union(
user2.Roles);
```

Intersect｜集合操作符｜集合交

```csharp
var roles = 
user1.Roles.Intersect(
user2.Roles);
```

Except｜集合操作符｜集合差

```csharp
var roles = 
user1.Roles.Except(
user2.Roles);
```

AsEnumerable｜轉換操作符｜用於把一個IEnumerable的派生類型轉化為IEnumerable類型

AsQueryable｜轉換操作符｜IEnumerable(Of T)轉化為IQueryable(Of T).

ToArray｜轉換操作符｜轉換為陣列

ToList｜轉換操作符｜轉換為List

ToDictionary｜轉換操作符｜轉換為一對一的字典（鍵-值對的集合）

ToLookup｜轉換操作符｜轉換為一對多的字典（鍵-值集的集合）

OfType｜轉換操作符｜獲取指定類型的元素組成一個陣列

```csharp
object[] numbers = { null, 1.0,
 "two", 3, "four", 5, 
"six", 7.0 };
var doubles = 
numbers.OfType<double>();
```

Cast｜轉換操作符｜把序列的所有元素轉換為指定類型

SequenceEqual｜相等操作符｜兩個序列的元素依次相同返回真。使用元素所屬類的IEqualityComparer(Of T) 泛型界面做相等比較

First｜元素操作符｜返回序列第一個元素（或滿足條件第一個元素），沒有則異常

FirstOrDefault｜元素操作符｜返回序列第一個元素，沒有則返回空或默認值

```csharp
var user = 
db.Users.FirstOrDefault(
o => o.Roles.Count == 3);
```

Last｜元素操作符｜返回序列最後一個元素，沒有則異常

LastOrDefault｜元素操作符｜返回序列最後一個元素，沒有則返回空或默認值

```csharp
var user = 
db.Users.LastOrDefault(
o => o.Roles.Count == 3);
```

Single｜元素操作符｜返回序列唯一元素，如果沒有元素或多個元素則異常

SingleOrDefault｜元素操作符｜返回序列唯一元素，如果多個元素則異常｜`var user = db.Users.SingleOrDefault(o => o.Account == "Apollo");`

ElementAt｜元素操作符｜返回序列指定元素，失敗則異常

ElementAtOrDefault｜元素操作符｜返回序列指定元素，失敗則空或默認值

DefaultIfEmpty｜元素操作符｜返回序列，如果序列為空則返回元素的默認值

```vb
For Each number As Integer 
In numbers.DefaultIfEmpty()   
output.AppendLine(number)
Next 
```

All｜量詞操作符｜序列所有元素滿足條件則為真

```csharp
var result 
= db.Users.All(
o => o.Roles.Count == 3);
```

Any｜量詞操作符｜序列有一個元素滿足條件則為真

```csharp
var result 
= db.Users.Any(
o => o.Roles.Count == 3);
```

Contains｜量詞操作符｜是否包含一個元素

```csharp
var result = 
db.Users.Where(
o => o.Roles.Count == 3
).Contains(user1);
```

Count｜聚合統計操作符｜計數，可選一個謂詞

```csharp
var result 
= db.Users.Count
(o => o.Roles.Count == 3);
```

LongCount｜聚合統計操作符｜計數，返回Int64類型

Sum｜聚合統計操作符｜求和，可選對一個lambda函數表達式

```csharp
var result 
= db.Users.Sum
(o => o.Roles.Count);
```

Min｜聚合統計操作符｜最小值，可選對一個lambda函數表達式

```csharp
var result 
= db.Users.Min
(o => o.Roles.Count);
```

Max｜聚合統計操作符

```csharp
var result 
= db.Users.Max
(o => o.Roles.Count);
```

Average｜聚合統計操作符

```csharp
var result 
= db.Users.Average
(o => o.Roles.Count);
```

Aggregate｜聚合統計操作符｜參數為一個委託，在序列的每個元素上執行該委託。委託的第一個參數為當前累計值，第二個參數為當前元素，返回值為新的累計值

```vb
Dim reversed As String = 
words.Aggregate(
Function(ByVal current,
 ByVal word) 
word & " " & current)
```

equals/Equals｜關鍵字｜用於Join子句

from/From｜關鍵字

in/In｜關鍵字｜指出數據源

into/Into｜關鍵字｜用於Group By子句

key｜關鍵字｜用於Group By子句的無名類型

let｜關鍵字｜給表達式定義別名

```vb
From prod In products 
Let Discount = 
prod.UnitPrice * 0.1 
Where Discount >= 50 
Select prod.ProductName, 
prod.UnitPrice, Discount
```

Group｜關鍵字｜在GroupBy子句的Into中用於辨識分組結果

```vb
From num In numbers 
Group num By 
remainder5 = 
(num Mod 5) Into Group
```

Range｜方法｜產生一個整數序列

```vb
From n In 
Enumerable.Range(100, 50)
```

Repeat｜方法｜產生一個整數序列

```vb
From n In  
Enumerable.Repeat(7, 10)
```

## LINQ的各式言語支援度

下列的言語支持LINQ。

- C# 3.0
- F# 1.1.8.1
- Visual Basic 2008（9.0）

註：C++/CLI尚未支援LINQ。但是有第三方的C++套件，以及第三方的PHP套件

## LINQ的範例

一個簡單例子：

```csharp
using System;
using System.Linq;

namespace DuckTyping
{
    internal class Program
    {
        private static void Main()
        {
            int[] array = { 1, 5, 2, 10, 7 };  
            // Select squares of all odd numbers in the array sorted in descending order
            var results = from x in array
                                     where x % 2 == 1
                                     orderby x descending
                                     select x * x;
            foreach (var result in results)
            {
                Console.WriteLine(result); 
            }
        }
    }
}
```

輸出：

49 25 1

另一個例子：

```csharp
// the Northwind type is a subclass of DataContext created by SQLMetal
// Northwind.Orders is of type Table<Order>
// Northwind.Customers is of type Table<Customer>

Northwind db = new Northwind(connectionString);
 
// use 'var' keyword because there is no name for the resultant type of the projection
 
var q =  from o in db.Orders
         from c in db.Customers
         where o.Quality == "200" && (o.CustomerID == c.CustomerID)
         select new { o.DueDate, c.CompanyName, c.ItemID, c.ItemName };
 
// q is now an IEnumerable<T>, where T is the anonymous type generated by the compiler

foreach (var t in q)
{
    // t is strongly typed, even if we can't name the type at design time
 
    Console.WriteLine("DueDate Type = {0}", t.DueDate.GetType());
    Console.WriteLine("CompanyName (lowercased) = {0}", t.CompanyName.ToLower());
    Console.WriteLine("ItemID * 2 = {0}", t.ItemID * 2);
}
```

## Visual Studio支持

LINQ目前由Visual Studio 2008、2010、2012、2013、2015、2017、2019支持。

## 語言擴展

微軟同樣提供了LINQExtender，允許使用者在不了解LINQ實現細節的情況下，編寫自己的LINQ擴展。

如：LINQ to Twitter,LINQ to Oracle,LINQ to Active Directory等

## 外部連結

- Official Microsoft LINQ Project（已失效）
- 101 C# LINQ Samples
- 101 Visual Basic LINQ Samples
- Microsoft LINQ forum
- LINQ page on NetFXGuide.com
- LINQ wiki
- LINQExtender