語言集成查詢
語言集成查詢(英語: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),採用值語義。
下列使用匿名型別的程式碼:
[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;
}
會由編譯器改寫為:
[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;
}
擴展方法 (Extension method)
Lambda表達式 (Lambda expression)
表達式樹 (Expression tree)
查詢表達式語法
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方法語法的查詢,返回數組中的偶數:
int[] ints={1,2,3,4};
var result = ints.Where(p => p % 2 == 0).ToArray();
對比流利語法和C#的傳統語法:
// 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)方法集合。基本語法如下:
using (var db = new EntityContext())
{
var roles = from o in db.Users
where o.Account == "Apollo"
select o.Roles;
…
}
標準查詢操作符和Lambda表達式的關係非常密切。編譯器會將上述表達式轉化為下述以Lambda表達式為參數的顯式擴展方法調用序列:
using (var db = new EntityContext())
{
var roles = db.Users.Where(o => o.Account == "Apollo").Select(o => o.Roles);
}
操作符 | 類別 | 語義 | 流利語法示例 | 查詢表達式語法示例 |
---|---|---|---|---|
Where | 篩選操作符(Restriction) | Predicate→bool | var user =
db.Users.Where(
o => o.Roles != null);
|
var users =
from o in db.Users
where o.Roles != null
select o;
|
Select | 投影操作符(Projection) | 將對象投影為一個匿名類型實例 TSource→TResult |
var users =
db.Users.Select
(o => new { o.Account, o.Password });
|
var users =
from o in db.Users
select new { o.Account, o.Password };
|
SelectMany | 投影操作符(Projection) | 返回多行結果,用於多表的交叉連接(cross join) | Dim res = Employees.SelectMany(Function(e)
e.Family.Select(Function(c)c.name))
| |
Skip | 分塊操作符(Partitioning) | 跳過前n個元素 | var users =
db.Users.OrderBy(
o => o.Roles.Count
).Skip(10);
|
|
SkipWhile | 分塊操作符(Partitioning) | 跳過起始處使條件為真的所有元素 | var users =
db.Users.OrderBy(
o => o.Roles.Count).SkipWhile(
o => o.Roles == 3);
|
|
Take | 分塊操作符(Partitioning) | 返回開頭之處的n個元素 | var users =
db.Users.OrderBy(
o => o.Roles.Count).Take(5);
|
|
TakeWhile | 分塊操作符(Partitioning) | 返回起始處使條件為真的所有元素 | var users =
db.Users.OrderBy(
o => o.Roles.Count).TakeWhile(
o => o.Roles.Count == 3);
|
|
Join | 連接操作符 | 內連接兩個或多個表,僅限於Equals運算符 | 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,右側集合匹配於左側集合鍵值的元素被分組 | From cust In customers Group
Join ord In orders
On cust.CustomerID
Equals ord.CustomerID
Into CustomerOrders = Group,
OrderTotal = Sum(ord.Total)
| |
Concat | 合併操作符 | 用於連接兩個序列 | returnValue = firstSeq.Concat(secondSeq) | |
OrderBy | 排序操作符(Ordering) | 升序排列 TSource→TKey |
var users =
db.Users.OrderBy(
o => o.Roles.Count);
|
var users =
from o in db.Users
orderby o.Roles.Count
select o;
|
OrderByDescending | 排序操作符(Ordering) | 降序排列 | var users =
db.Users.OrderByDescending(
o => o.Roles.Count);
|
var users =
from o in db.Users
orderby o.Roles.Count descending
select o;
|
ThenBy | 排序操作符(Ordering) | 只能對IOrderedEnumerable接口對象使用 | ||
ThenByDescending | 排序操作符(Ordering) | 只能對IOrderedEnumerable接口對象使用 | ||
Reverse | 排序操作符(Ordering) | 只能對IOrderedEnumerable接口對象使用 | ||
GroupBy | 分組操作符 | var users =
db.Users.GroupBy(
o => o.Roles.Count);
|
var users =
from o in db.Users
group o by o.Roles.Count into g
select new { RoleCount = g.Key, Group = g };
| |
Distinct | 集合操作符 | 去重複 | var roles =
user.Roles.Distinct();
|
|
Union | 集合操作符 | 集合併,還去重複 | var roles =
user1.Roles.Union(
user2.Roles);
|
|
Intersect | 集合操作符 | 集合交 | var roles =
user1.Roles.Intersect(
user2.Roles);
|
|
Except | 集合操作符 | 集合差 | var roles =
user1.Roles.Except(
user2.Roles);
|
|
AsEnumerable | 轉換操作符 | 用於把一個IEnumerable的派生類型轉化為IEnumerable類型 | ||
AsQueryable | 轉換操作符 | IEnumerable(Of T)轉化為IQueryable(Of T). | ||
ToArray | 轉換操作符 | 轉換為數組 | ||
ToList | 轉換操作符 | 轉換為List | ||
ToDictionary | 轉換操作符 | 轉換為一對一的字典(鍵-值對的集合) | ||
ToLookup | 轉換操作符 | 轉換為一對多的字典(鍵-值集的集合) | ||
OfType | 轉換操作符 | 獲取指定類型的元素組成一個數組 | object[] numbers = { null, 1.0,
"two", 3, "four", 5,
"six", 7.0 };
var doubles =
numbers.OfType<double>();
| |
Cast | 轉換操作符 | 把序列的所有元素轉換為指定類型 | ||
SequenceEqual | 相等操作符 | 兩個序列的元素依次相同返回真。 使用元素所屬類的IEqualityComparer(Of T) 泛型界面做相等比較 |
||
First | 元素操作符 | 返回序列第一個元素(或滿足條件第一個元素),沒有則異常 | ||
FirstOrDefault | 元素操作符 | 返回序列第一個元素,沒有則返回空或默認值 | var user =
db.Users.FirstOrDefault(
o => o.Roles.Count == 3);
|
|
Last | 元素操作符 | 返回序列最後一個元素,沒有則異常 | ||
LastOrDefault | 元素操作符 | 返回序列最後一個元素,沒有則返回空或默認值 | var user =
db.Users.LastOrDefault(
o => o.Roles.Count == 3);
|
|
Single | 元素操作符 | 返回序列唯一元素,如果沒有元素或多個元素則異常 | ||
SingleOrDefault | 元素操作符 | 返回序列唯一元素,如果多個元素則異常 | var user = db.Users.SingleOrDefault(o => o.Account == "Apollo");
|
|
ElementAt | 元素操作符 | 返回序列指定元素,失敗則異常 | ||
ElementAtOrDefault | 元素操作符 | 返回序列指定元素,失敗則空或默認值 | ||
DefaultIfEmpty | 元素操作符 | 返回序列,如果序列為空則返回元素的默認值 | For Each number As Integer
In numbers.DefaultIfEmpty()
output.AppendLine(number)
Next
| |
All | 量詞操作符 | 序列所有元素滿足條件則為真 | var result
= db.Users.All(
o => o.Roles.Count == 3);
|
|
Any | 量詞操作符 | 序列有一個元素滿足條件則為真 | var result
= db.Users.Any(
o => o.Roles.Count == 3);
|
|
Contains | 量詞操作符 | 是否包含一個元素 | var result =
db.Users.Where(
o => o.Roles.Count == 3
).Contains(user1);
|
|
Count | 聚合統計操作符 | 計數,可選一個謂詞 | var result
= db.Users.Count
(o => o.Roles.Count == 3);
|
|
LongCount | 聚合統計操作符 | 計數,返回Int64類型 | ||
Sum | 聚合統計操作符 | 求和,可選對一個lambda函數表達式 | var result
= db.Users.Sum
(o => o.Roles.Count);
|
|
Min | 聚合統計操作符 | 最小值,可選對一個lambda函數表達式 | var result
= db.Users.Min
(o => o.Roles.Count);
|
|
Max | 聚合統計操作符 | var result
= db.Users.Max
(o => o.Roles.Count);
|
||
Average | 聚合統計操作符 | var result
= db.Users.Average
(o => o.Roles.Count);
|
||
Aggregate | 聚合統計操作符 | 參數為一個委託,在序列的每個元素上執行該委託。 委託的第一個參數為當前累計值,第二個參數為當前元素, 返回值為新的累計值 |
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 | 關鍵字 | 給表達式定義別名 | From prod In products
Let Discount =
prod.UnitPrice * 0.1
Where Discount >= 50
Select prod.ProductName,
prod.UnitPrice, Discount
| |
Group | 關鍵字 | 在GroupBy子句的Into中用於辨識分組結果 | From num In numbers
Group num By
remainder5 =
(num Mod 5) Into Group
| |
Range | 方法 | 產生一個整數序列 | From n In
Enumerable.Range(100, 50)
| |
Repeat | 方法 | 產生一個整數序列 | From n In
Enumerable.Repeat(7, 10)
|
LINQ的各式言語支援度
下列的言語支持LINQ。
- C# 3.0
- F# 1.1.8.1
- Visual Basic 2008(9.0)
LINQ的範例
一個簡單例子:
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
另一個例子:
// 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等
參考文獻
外部連結
參見
- 物件關聯對映(ORM)