语言集成查询

一項為.Net提供SQL自然查詢語法的技術

语言集成查询(英語: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.Userswhere o.Roles != nullselect o;
Select投影操作符(Projection)将对象投影为一个匿名类型实例
TSource→TResult
var users = db.Users.Select(o => new { o.Account, o.Password });
var users = from o in db.Usersselect 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.Categoriesjoin p in nWEntities.Productson c.CategoryID equals p.CategoryIDinto productsByCategoryIDselect 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.Usersorderby o.Roles.Countselect o;
OrderByDescending排序操作符(Ordering)降序排列
var users = db.Users.OrderByDescending(o => o.Roles.Count);
var users = from o in db.Usersorderby o.Roles.Count descendingselect 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.Usersgroup o by o.Roles.Count into gselect 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++/CLI尚未支援LINQ。但是有第三方的C++套件[1],以及第三方的PHP套件[2]

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);             }        }    }}

输出:49251

另一个例子:

// 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 compilerforeach (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等

参考文献编辑

  1. ^ LINQ for C++. [2014-02-18]. (原始内容存档于2014-02-16). 
  2. ^ PHPLinq. [2014-02-18]. (原始内容存档于2014-02-22). 

外部連結编辑

参见编辑