?Linq(Language Integrated Query,集成查詢語言),顧名思義就是用來查詢數(shù)據(jù)的一種語言(可以看作是一組功能、框架特性的集合)。在.NETFramework3.5
(大概2007年)引入C#,用統(tǒng)一的C#語言快速查詢各種數(shù)據(jù),如數(shù)據(jù)庫、XML文檔、對(duì)象集合等等。Linq的誕生對(duì) C# 編程領(lǐng)域產(chǎn)生了深遠(yuǎn)而持久的影響,改變了開發(fā)人員對(duì)查詢的思考方式。
- 使用簡(jiǎn)單:統(tǒng)一語法(鏈?zhǔn)椒椒ㄕZ法、類似SQL的查詢語法),智能提示。
- 類型安全:編譯時(shí)強(qiáng)類型檢查,減少運(yùn)行時(shí)錯(cuò)誤。
- 延遲執(zhí)行,查詢本身只是構(gòu)建了一個(gè)表達(dá)式,在真正使用的時(shí)候(foreach、ToList、查詢數(shù)據(jù)庫)才會(huì)執(zhí)行。
- 支持多種數(shù)據(jù)源:內(nèi)存中的集合,以及各種外部數(shù)據(jù)庫。

Linq支持查詢?nèi)魏螌?shí)現(xiàn)了IEnumerable<T>
接口的集合類型,基本上所有集合數(shù)據(jù)都支持Linq查詢。如下示例:大于 5 的偶數(shù),并倒敘排列取前三名
|
var query = arr.Where(n => n > 5 && n % 2 == 0).OrderByDescending(n => n).Take(3); |
Linq 有兩種語法風(fēng)格,如下實(shí)例代碼,一種是常規(guī)C#方法調(diào)用方式,另外一種是類似SQL的查詢表達(dá)式。這兩種語法其本質(zhì)是一樣的,編譯后的中間語言(IL)是一樣的,確實(shí)僅僅只是語法形式不同而已。
??鏈?zhǔn)椒椒?/span>:就是字面意思,函數(shù)式方法調(diào)用。這些方法都來自 IEnumerable 接口或 IQueryable 接口的擴(kuò)展方法,這些方法提供了過濾、聚合、排序等多種查詢功能。
??查詢表達(dá)式:查詢表達(dá)式由一組用類似于 SQL 的聲明性語法所編寫的子句組成。 每個(gè)子句依次包含一個(gè)或多個(gè) C# 表達(dá)式,而這些表達(dá)式可能本身就是查詢表達(dá)式,或者包含查詢表達(dá)式。查詢表達(dá)式必須以 from
子句開頭,且必須以 select
或 group
子句結(jié)尾。
|
var query = arr.Where(n => n > 5 && n % 2 == 0).OrderByDescending(n => n).Take(3); |
|
var query2 = (from n in arr |
where n > 5 && n % 2 == 0 |
orderby n descending |
select n).Take(3); |
比較 | 鏈?zhǔn)椒椒?/span> | 查詢表達(dá)式(SQL) |
---|
特點(diǎn) | 鏈?zhǔn)椒椒ㄕ{(diào)用,函數(shù)式編程 | 類似SQL語句,自然語言,容易掌握 |
語法形式 | 點(diǎn)點(diǎn)點(diǎn)鏈?zhǔn)椒椒ㄕ{(diào)用,Where().Select().Order() | 以from 開頭:from...where...select |
常用方法/語法 | System.Linq 上提供的擴(kuò)展方法或第三方擴(kuò)展:Where、OrderBy、Select、Skip、Take、Union | 僅支持編譯器識(shí)別的關(guān)鍵字:from、where、orderby、group、join、let、select、into、in、on等 |
本質(zhì) | System.Linq 提供的擴(kuò)展方法調(diào)用 | 編譯為標(biāo)準(zhǔn)查詢運(yùn)算符方法調(diào)用,編譯結(jié)果和鏈?zhǔn)椒椒ㄒ粯?/span> |
功能完整性 | 完整的Linq功能 | 有些能力沒有對(duì)應(yīng)語法(如Max),需要結(jié)合鏈?zhǔn)椒椒ㄊ褂?/td> |

?? 兩種編寫方式編譯后生成的IL代碼實(shí)際上是一樣的,也可以混合使用,因此他們并沒有性能差異。
查詢表達(dá)式并不能實(shí)現(xiàn)獲取前3個(gè)元素,此時(shí)就需要兩者混合使用,
var query = from u in list |
where u.Age>14 |
group u by u.Address into gu |
orderby gu.Count() descending |
select (gu.Key,gu.Count()); |
query = query.Take(3); |
LINQ 提供了兩種用途的架構(gòu):針對(duì)本地(內(nèi)存)對(duì)象的本地查詢,以及針對(duì)遠(yuǎn)程數(shù)據(jù)源(數(shù)據(jù)庫)的解釋性查詢。兩者的語法形式基本一樣,都支持鏈?zhǔn)椒椒?、查詢表達(dá)式。
??本地查詢:實(shí)現(xiàn)了針對(duì)IEnumerable
的內(nèi)存集合(數(shù)組、List)的查詢,其Linq的擴(kuò)展方法都在 System.Linq.Enumerable 類中。查詢只是構(gòu)建了一個(gè)可枚舉的迭代裝飾器序列,延遲在使用(消費(fèi))數(shù)據(jù)時(shí)執(zhí)行。
??解釋查詢:解釋查詢是描述性的,實(shí)現(xiàn)了針對(duì)IQueryable
(Table、DbSet)的遠(yuǎn)程數(shù)據(jù)查詢,對(duì)應(yīng)擴(kuò)展方法都在 System.Linq.Queryable 類中。他們?cè)谶\(yùn)行時(shí)生成表達(dá)式樹,并進(jìn)行解釋為SQL語句,在數(shù)據(jù)庫中執(zhí)行該SQL語句并獲取數(shù)據(jù)。
比較 | 本地查詢 Enumerable | 解釋查詢 Queryable |
---|
操作對(duì)象 | 內(nèi)存中的集合(IEnumerable<T> ) | 外部數(shù)據(jù)源的查詢接口(IQueryable<T> ) |
延遲執(zhí)行 | 支持,真正使用(消費(fèi))數(shù)據(jù)時(shí)才執(zhí)行,如 foreach、ToList | 支持,消費(fèi)數(shù)據(jù)時(shí)才翻譯成SQL并在數(shù)據(jù)庫中執(zhí)行獲取數(shù)據(jù) |
執(zhí)行原理 | 參數(shù)為委托方法,C#內(nèi)部執(zhí)行委托、迭代器 | 參數(shù)為表達(dá)式樹,LINQ Provider 在運(yùn)行時(shí)遍歷該樹轉(zhuǎn)換為目標(biāo)語言(如 SQL) |
誰來執(zhí)行 | CLR本地執(zhí)行,數(shù)據(jù)在內(nèi)存中 | 數(shù)據(jù)庫執(zhí)行SQL,數(shù)據(jù)在數(shù)據(jù)庫中 |
執(zhí)行過程 | 本地逐個(gè)元素迭代調(diào)用委托 | 數(shù)據(jù)庫中執(zhí)行SQL,返回查詢結(jié)果 |
使用場(chǎng)景 | List、Array、普通內(nèi)存數(shù)據(jù) | Entity Framework、LINQ to SQL、MongoDB 查詢 |
語法 | 都支持鏈?zhǔn)椒椒?、表達(dá)式查詢 | 同樣支持鏈?zhǔn)椒椒ā⒈磉_(dá)式查詢 |
Linq方法在哪里? | System.Linq.Enumerable 靜態(tài)類 | System.Linq.Queryable 類,方法和 Enumerable 大部分對(duì)應(yīng)。有些方法并不能生成數(shù)據(jù)庫兼容的SQL語法。 |
擴(kuò)展性 | 內(nèi)存查詢支持任意C#方法,擴(kuò)展性強(qiáng) | 受限,只能使用數(shù)據(jù)庫兼容的方法。如正則表達(dá)式SQLServer就不支持。 |
結(jié)合使用 | 本地?cái)?shù)據(jù)只能用本地查詢 | 遠(yuǎn)程數(shù)據(jù)可以結(jié)合本地查詢混用。 |
IQueryable 繼承自 IEnumerable,因此解釋查詢可以轉(zhuǎn)換為本地查詢,query.AsEnumerable()
,不過需謹(jǐn)慎使用,會(huì)將數(shù)據(jù)庫的相應(yīng)數(shù)據(jù)都加載到內(nèi)存中。
public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable |
{ |
} |
??表達(dá)式樹是一個(gè)微型的代碼DOM結(jié)構(gòu),樹中的節(jié)點(diǎn)是Expression類型的節(jié)點(diǎn),涵蓋各種語法形式,如參數(shù)、變量、常量、賦值、比較、循環(huán)等等。表達(dá)式樹可以轉(zhuǎn)換(Compile)為委托,反之則不能。
延遲執(zhí)行是指查詢代碼不會(huì)立刻執(zhí)行,而是在正真取數(shù)的時(shí)候才會(huì)執(zhí)行。他是Linq最主要的特點(diǎn),是優(yōu)點(diǎn),也不全是,有些需要注意的地方。
- 并不是所有的Linq方法都是延遲的,如:First()、Last()、ToArray()、ToList(),及Count、Max等聚合計(jì)算方法會(huì)立即執(zhí)行。
- 如果數(shù)據(jù)源變了,結(jié)果也會(huì)變化。
List<int> list = [2,3,9,4,5]; |
var query = list.Where(s=>s>5); |
Console.WriteLine(query.Sum()); |
list.Add(6); |
Console.WriteLine(query.Sum()); |
- 重復(fù)取數(shù)時(shí),查詢也會(huì)重復(fù)執(zhí)行,可能會(huì)浪費(fèi)性能,特別是復(fù)雜、耗時(shí)的查詢。避免的方式就是
query.ToList()
一次性立即獲取數(shù)據(jù)。 - Lambda變量捕獲,變量的值在真正執(zhí)行查詢的時(shí)候才會(huì)獲取,這是方法閉包的特點(diǎn)。
List<int> list = [2,3,9,4,5]; |
int n = 5; |
var query = list.Where(s=>s>n); |
n = 4; |
Console.WriteLine(query.Sum()); |
為了支持延遲執(zhí)行,Linq內(nèi)部封裝了很多迭代裝飾器,偷偷看了下源碼,如 WhereIterator、SelectEnumerableIterator、ReverseIterator、UnionIterator 等,都是Linq內(nèi)部的迭代裝飾器。迭代裝飾器會(huì)保留輸入序列的引用及其他相關(guān)參數(shù),僅當(dāng)枚舉結(jié)果時(shí)才會(huì)執(zhí)行。
迭代序列裝飾器本身繼承自IEnumerable,因此就支持裝飾器之間的嵌套。下面為迭代裝飾器序列基類的源碼 Iterator.cs。
internal abstract class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource> |
{ |
private readonly int _threadId; |
internal int _state; |
internal TSource _current = default!; |
} |
內(nèi)存集合的Linq擴(kuò)展方法,基本都來自Enumerable類,參考官方 Enumerable 類。用于數(shù)據(jù)庫的解釋性查詢方法在 System.Linq.Queryable 類中,方法和 Enumerable 基本上都是對(duì)應(yīng)的?;旧纤械腖inq方法都在這里匯總:
方法 | 說明 |
---|
Chunk(Int32) | 分塊拆分為多個(gè)固定大小的數(shù)組,返回IEnumerable<TSource[]> ,內(nèi)部每次迭代會(huì)構(gòu)建一個(gè)數(shù)組new TSource[arraySize] |
Append(T) | 末尾追加一個(gè)元素,原理是內(nèi)部構(gòu)建了一個(gè)新的迭代器AppendPrepend1Iterator 實(shí)現(xiàn)返回這個(gè)元素。 |
Prepend(T) | 在前面追加一個(gè)元素,原理同上,是同一個(gè)AppendPrepend1Iterator |
??聚合計(jì)算,立即執(zhí)行 |
|
Count() | 獲取集合中元素的數(shù)量,可指定條件參數(shù)Func。arr.Count() ,內(nèi)部原理比較簡(jiǎn)單,如果集合是ICollection 等,則直接獲取Count ,否則只能e.MoveNext() 一個(gè)一個(gè)的數(shù)了。 |
TryGetNonEnumeratedCount | 獲取元素?cái)?shù)量,在不真正遍歷(不枚舉)集合的情況下,盡量嘗試快速拿到集合元素的數(shù)量 |
Max() | 返回最大的那個(gè)元素。截止.NET8 ,整數(shù)類型用了Vector提升性,其他循環(huán)比較,性能一般??。 |
Min() | 返回最小的那個(gè)元素,性能原理同Max |
Average() | 計(jì)算平均值,對(duì)于數(shù)值類型,內(nèi)部用到了Vector?,性能還是不錯(cuò)的。var a = arr.Average() |
Sum() | 求和,arr1.Sum() |
Aggregate(Func) | 執(zhí)行累加器函數(shù),函數(shù)的的輸出為作為下一輪迭代的輸入,依次迭代執(zhí)行。 示例,計(jì)算序列最大值:var max = arr.Aggregate((acc,n)=>acc>n?acc:n) |
??條件判斷 |
|
Contains(T) | 判斷是否包含指定元素,返回bool,可指定比較器。bool f = arr.Contains(6) |
Any() | 集合是否包含元素,判斷集合是否不為空。if(arr.Any()){} |
Any(Func) | 集合是否包含指定條件的元素,示例:是否有人考試滿分,bool flag = arr.All(n=>n==100) |
All(Func) | 所有元素是否滿足條件,示例:是否所有同學(xué)都及格了,bool flag = arr.All(n=>n>=60) |
SequenceEqual(IEnumerable) | 序列相等比較,比較兩個(gè)序列是否相同,長(zhǎng)度相同、每個(gè)元素相等則返回True |
??元素選擇 |
|
First() | 返回第一個(gè)元素,如果一個(gè)都沒有拋出異常,arr1.First() |
FirstOrDefault() | 返回第一元素,如果一個(gè)都沒有則返回默認(rèn)值,arr1.FirstOrDefault() |
Last() | 返回最后一個(gè)元素,如果一個(gè)都沒有拋出異常。如果不是常規(guī)集合,會(huì)foreach 循環(huán)所有??。 |
LastOrDefault() | 同上,如果一個(gè)都木有則返回默認(rèn)值 |
Single()、SingleOrDefault() | 獲取唯一元素,如果元素?cái)?shù)量大于1則拋出異常。這個(gè)方法在數(shù)據(jù)庫按主鍵查詢時(shí)比較有用。 |
ElementAt(Index) | 返回指定索引Index 位置的元素,arr.ElementAt(0) 。還有個(gè)更安全的 ElementAtOrDefault |
DefaultIfEmpty(defaultT) | 如果集合為空(集合中沒有元素)返回含一個(gè)默認(rèn)值的IEnumerable,否則返回原序列。 |
??篩選查詢 |
|
Where(Func) | 條件查詢,最常用的Linq函數(shù)了,arr1.Where(s=>s>5) |
Select(selector) | 返回指定Key(元素選擇處理器結(jié)果)的集合,list.Select(s=>s.Name+s.Age) |
SelectMany() | 將每個(gè)元素的“內(nèi)部集合”展開合并為一個(gè)大集合,list.SelectMany(s=>s.Name.Split('-')) |
Distinct() | 去重,arr.Distinct() ,內(nèi)部使用HashSet<TSource> 來去重。DistinctBy>可指定鍵Key。 |
OfType() | 根據(jù)類型T篩選集合,源碼中用obj is TResult 來篩選,不符合的丟棄。list.OfType<double>() |
Skip(int count) | 跳過指定數(shù)量的元素,返回剩余的元素,arr1.Skip(5) |
SkipLast(int count) | 忽略后面的元素,返回前面剩余的元素。arr1.SkipLast(3) |
SkipWhile(Func) | 從開頭跳過符合條件的元素,直到遇到不符合條件時(shí)停下,返回剩下的元素。 |
Take(int count) | 返回前n個(gè)元素,Skip的逆運(yùn)算,Take(3) |
TakeLast(int count) | 返回最后n個(gè)元素,arr1.TakeLast(3) |
TakeWhile(Func) | 從開頭返回符合條件的元素,直到遇到不符合條件時(shí)停下,與SkipWhile相反arr1.TakeWhile(s=>s<5) |
??排序分組 |
|
Order() | 升序排列集合,arr2.Order() |
OrderBy(TKey) | 指定Key鍵升序排列集合,list.OrderBy(s=>s.Age) |
OrderByDescending(TKey) | 指定Key鍵降序排列集合,list.OrderByDescending(s=>s.Age) |
ThenBy、ThenByDescending | 二次排序,跟著OrderBy使用,設(shè)置第二排序鍵。list.OrderBy(s=>s.Grade).ThenBy(s=>s.Age) |
Reverse() | 反轉(zhuǎn)序列中元素的順序,arr2.Reverse() 。內(nèi)部源碼是創(chuàng)建了一個(gè)數(shù)組來實(shí)現(xiàn)翻轉(zhuǎn),性能不佳??,數(shù)組推薦使用Array.Reverse() ,原地翻轉(zhuǎn),不會(huì)創(chuàng)建額外對(duì)象。 |
GroupBy | 按指定的Key分組,返回一個(gè)分組集合IGrouping<TKey, TSource> ,list.GroupBy(s=>s.Name) |
GroupJoin | 帶分組的連接(Join)操作,類似Sql中的Left Join + 分組,每個(gè)「左邊元素」對(duì)應(yīng)到「右邊的一組元素」 |
??多集合操作 |
|
Union(IEnumerable) | 并集,合并兩個(gè)集合并去重,arr1.Union(arr2) |
Intersect(IEnumerable) | 交集(Intersect /??nt??sekt/ 相交),返回兩個(gè)集合都包含的元素。IntersectBy 可指定鍵Key。 |
Except(IEnumerable) | 移除(Except /?k?sept/ 除外)arr1.Except(arr2) 移除arr2 中也存在的元素。ExceptBy可指定鍵Key。 |
Concat(IEnumerable) | “合并”兩個(gè)序列集合(),內(nèi)部由私有的ConcatIterator 實(shí)現(xiàn)的連接迭代,arr.Concat([3]) |
Join(arr2, k2,k1,Func) | 兩個(gè)“表”內(nèi)連接,類似Sql中的 Inner Join,用于兩個(gè)不同類型元素的的連接,兩個(gè)表Key匹配的元素合并 |
Zip | 就像拉鏈(zipper)一樣,把兩個(gè)序列一對(duì)一地配對(duì)合并成一個(gè)新序列,arr1.Zip(arr2,(n1,n2)=>n1+n2) |
??轉(zhuǎn)換,ToXX立即執(zhí)行 | ?謹(jǐn)慎使用,會(huì)創(chuàng)建新的集合對(duì)象 |
Cast() | 強(qiáng)制類型轉(zhuǎn)換,內(nèi)部使用強(qiáng)制轉(zhuǎn)換“(TResult)obj ” |
ToArray() | 從 IEnumerable 創(chuàng)建新數(shù)組,慎用。var narr = arr1.Order().ToArray() |
ToList() | 從 IEnumerable 創(chuàng)建新List,arr1.Take(3).ToList() |
ToHashSet | 從 IEnumerable 創(chuàng)建新HashSet(不可重復(fù)集合,自動(dòng)去重),arr1.ToHashSet() |
ToDictionary() | 從 IEnumerable 創(chuàng)建新字典Dictionary<TK,TV> ,list.ToDictionary(s=>s.Name,s=>s.Age) |
ToLookup() | 從 IEnumerable 創(chuàng)建新 Lookup(分組的字典),arr1.ToLookup(s=>s%2) |
??其他 |
|
Range(start, end) | 靜態(tài)方法,創(chuàng)建一個(gè)連續(xù)的序列,可用來創(chuàng)建測(cè)試數(shù)據(jù),Enumerable.Range(1,10).ToArray() |
Repeat(T, count) | 靜態(tài)方法,創(chuàng)建一個(gè)重復(fù)值的序列,Enumerable.Repeat(18,10) |
Empty() | 靜態(tài)方法,獲得一個(gè)空的序列,Enumerable.Empty<int>().Any(); //false |
AsEnumerable() | 返回自己,什么也不干。在Linq to SQL中可以強(qiáng)制讓后續(xù)操作在本地內(nèi)存中進(jìn)行,而不會(huì)翻譯成SQL。 |
|
|
根據(jù)用戶輸入條件,構(gòu)建動(dòng)態(tài)查詢條件,使用 Skip
和 Take
實(shí)現(xiàn)分頁。
var query = list.AsEnumerable(); |
if (!string.IsNullOrWhiteSpace(name)) |
query = query.Where(s => s.Name.Contains(name, StringComparison.OrdinalIgnoreCase)); |
if (age.HasValue) |
query = query.Where(s => s.Age == age); |
if (!string.IsNullOrWhiteSpace(address)) |
query = query.Where(s => s.Address.Contains(address)); |
|
query = query |
.Skip((pageNumber - 1) * pageSize) |
.Take(pageSize); |
|
var result = query.ToArray(); |
本地查詢擴(kuò)展是很容易的,基于IEnumerable<T>
實(shí)現(xiàn)擴(kuò)展方法即可。IQueryable
擴(kuò)展則要考慮數(shù)據(jù)庫的支持和映射,一般無需自定義擴(kuò)展。
|
public static IEnumerable<T> AlternateElements<T>(this IEnumerable<T> source) |
{ |
int index = 0; |
foreach (T element in source) |
{ |
if (index % 2 == 0) |
{ |
yield return element; |
} |
|
index++; |
} |
} |
|
|
var query = list.AlternateElements(); |
??版權(quán)申明:版權(quán)所有@安木夕,本文內(nèi)容僅供學(xué)習(xí),歡迎指正、交流,轉(zhuǎn)載請(qǐng)注明出處!原文編輯地址-語雀
該文章在 2025/7/2 9:59:31 編輯過