共用方式為


C 中的 LINQ 查詢簡介#

查詢 是從數據源擷取數據的表達式。 不同的數據源有不同的原生查詢語言,例如關係資料庫 SQL 和 XML 的 XQuery。 開發人員必須針對必須支援的每個數據源或數據格式,學習新的查詢語言。 LINQ 為數據源和格式類型提供一致的 C# 語言模型,以簡化這種情況。 在 LINQ 查詢中,您一律會使用 C# 物件。 您可以使用相同的基本編碼模式來查詢和轉換 XML 檔、SQL 資料庫、.NET 集合中的數據,以及當 LINQ 提供者可供使用時的任何其他格式。

查詢作業的三個部分

所有 LINQ 查詢作業都包含三個不同的動作:

  1. 取得數據源。
  2. 建立查詢。
  3. 執行查詢。

下列範例示範如何以原始程式碼表示查詢作業的三個部分。 為了方便起見,此範例會使用整數數位作為數據源;不過,相同的概念也適用於其他數據源。 本文其餘部分會參考此範例。

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery = from num in numbers
               where (num % 2) == 0
               select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

下圖顯示完整的查詢作業。 在 LINQ 中,查詢的執行與查詢本身不同。 換句話說,您不會藉由建立查詢變數來擷取任何數據。

完整 LINQ 查詢作業的圖表。

數據源

上述範例中的數據源是一個數位列,可支援泛型 IEnumerable<T> 介面。 這個事實表示可以使用LINQ進行查詢。 查詢會在 foreach 語句中執行,foreach 需要 IEnumerableIEnumerable<T>。 支援 IEnumerable<T> 或衍生介面的類型,例如泛型 IQueryable<T>,稱為 可查詢的類型。

可查詢的類型不需要修改或特殊處理,才能做為LINQ數據源。 如果源數據尚未以可查詢類型的形式存在於記憶體中,LINQ 提供者必須以此類方式表示。 例如,LINQ to XML 會將 XML 檔載入可查詢 XElement 類型:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

使用 EntityFramework時,您會在 C# 類別與資料庫架構之間建立物件關係型對應。 您可以針對物件撰寫查詢,並在運行時間 EntityFramework 處理與資料庫的通訊。 在下列範例中,Customers 代表資料庫中的特定數據表,而查詢結果的類型 IQueryable<T>衍生自 IEnumerable<T>

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

如需如何建立特定數據源類型的詳細資訊,請參閱各種 LINQ 提供者的檔。 不過,基本規則很簡單:LINQ 數據源是任何支援泛型 IEnumerable<T> 介面的物件,或繼承自它的介面,通常 IQueryable<T>

備註

支援非泛型介面 IEnumerableArrayList 類型也可以作為 LINQ 資料來源使用。 如需詳細資訊,請參閱 如何使用 LINQ (C#) 查詢 ArrayList

查詢內容

查詢會指定要從數據源或來源擷取的資訊。 或者,查詢也會指定在傳回之前,該信息應該如何排序、分組和成形。 查詢會儲存在查詢變數中,並使用查詢表示式初始化。 您可以使用 C# 查詢語法 來撰寫查詢。

上一個範例中的查詢會傳回整數陣列中的所有偶數。 查詢表示式包含三個子句:fromwhereselect。 (如果您熟悉 SQL,您注意到子句的順序與 SQL 中的順序相反。from 子句會指定數據源、where 子句套用篩選,而 select 子句會指定傳回專案的型別。 本節會詳細討論所有查詢子句。 目前,重點是在LINQ中,查詢變數本身不會採取任何動作,而且不會傳回任何數據。 它只會儲存在稍後執行查詢時產生結果所需的資訊。 如需查詢建構方式的詳細資訊,請參閱 標準查詢運算元概觀 (C#)

備註

您也可以使用方法語法來表示查詢。 如需詳細資訊,請參閱 LINQ 中的查詢語法和方法語法。

依執行方式分類標準查詢運算符

標準查詢運算子方法的 LINQ to Objects 實作會以兩種主要方式之一執行:立即延遲。 使用延遲執行的查詢運算子可以另外分成兩個類別:串流非串流

立即

立即執行表示數據源已讀取,且作業會執行一次。 所有傳回純量結果的標準查詢運算子都會立即執行。 這類查詢的範例包括 CountMaxAverageFirst。 這些方法在沒有明確的 foreach 語句的情況下執行,因為查詢本身必須使用 foreach 來傳回結果。 這些查詢會傳回單一值,而不是 IEnumerable 集合。 您可以使用 Enumerable.ToListEnumerable.ToArray 方法來強制 任何 查詢立即執行。 立即執行可讓您重複使用查詢結果,而不是查詢宣告。 結果會擷取一次,然後儲存以供日後使用。 下列查詢會傳回來源陣列中偶數的計數:

var evenNumQuery = from num in numbers
                   where (num % 2) == 0
                   select num;

int evenNumCount = evenNumQuery.Count();

若要強制立即執行任何查詢並快取其結果,您可以呼叫 ToListToArray 方法。

List<int> numQuery2 = (from num in numbers
                       where (num % 2) == 0
                       select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 = (from num in numbers
                 where (num % 2) == 0
                 select num).ToArray();

您也可以在查詢表示式後面立即放置 foreach 迴圈,以強制執行。 不過,藉由呼叫 ToListToArray,您也會將所有資料快取到單一的集合物件中。

已延遲

延遲執行表示作業不會在宣告查詢的程式代碼中執行。 只有在列舉查詢變數時,才會執行此作業,例如使用 foreach 語句。 執行查詢的結果取決於執行查詢時數據源的內容,而不是定義查詢時。 如果多次列舉查詢變數,則每次結果可能會有所不同。 幾乎所有傳回型別為 IEnumerable<T>IOrderedEnumerable<TElement> 的標準查詢運算符都是以延遲方式執行的。 延遲執行提供查詢重複使用的功能,因為每次對查詢結果進行迭代時,都會從資料來源擷取更新的資料。 下列程式代碼顯示延遲執行的範例:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

foreach 語句也是擷取查詢結果的位置。 例如,在上一個查詢中,反覆運算變數 num 在傳回的序列中保留每個值(一次一個)。

因為查詢變數本身永遠不會保存查詢結果,所以您可以重複執行它來擷取更新的數據。 例如,個別的應用程式可能會持續更新資料庫。 在 應用程式中,您可以建立一個查詢來擷取最新的數據,而您可以依間隔執行它來擷取更新的結果。

使用延後執行的查詢運算符可另外分類為串流或非串流。

串流

串流運算元不需要讀取所有源數據,才能產生元素。 在執行時,串流運算符會在讀取時在每個來源元素上執行其作業,並在適當時產生元素。 串流運算符會繼續讀取來源元素,直到產生結果項目為止。 這表示可能會讀取多個來源元素來產生一個結果元素。

非串流

非串流運算符必須先讀取所有來源資料,才能產生結果元素。 排序或群組之類的作業屬於此類別。 在執行時,非串流查詢運算符會讀取所有源數據、將其放入數據結構、執行作業,併產生產生的元素。

分類數據表

下表根據其執行方法,將每個標準查詢運算符方法分類。

備註

如果運算符在兩個數據行中標示,則作業會涉及兩個輸入序列,而且每個序列會以不同的方式進行評估。 在這些情況下,它一律是參數清單中的第一個序列,會以延遲的串流方式進行評估。

標準查詢運算符 傳回類型 立即執行 延後串流執行 延遲的非串流執行
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average 單一數值
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max 單一數值、TSourceTResult?
Min 單一數值、TSourceTResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum 單一數值
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] 陣列
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ to Objects

“LINQ to Objects” 是指直接搭配任何 IEnumerableIEnumerable<T> 集合使用 LINQ 查詢。 您可以使用 LINQ 來查詢任何可列舉的集合,例如 List<T>ArrayDictionary<TKey,TValue>。 集合可以是使用者定義或 .NET API 所傳回的類型。 在 LINQ 方法論中,您可以撰寫宣告式程式碼來描述您想要擷取的資料。 LINQ to Objects 提供使用 LINQ 進行程式設計的絕佳簡介。

LINQ 查詢比傳統 foreach 迴圈提供三個主要優點:

  • 它們更簡潔且更容易閱讀,尤其是在篩選多個條件時。
  • 它們提供功能強大的篩選、排序和分組功能,最少的應用程式程序代碼。
  • 它們可以移植到其他數據源,而不需要修改。

您想要對數據執行的作業越複雜,使用 LINQ 而非傳統迭代技術就越有好處。

將查詢的結果儲存在記憶體中

查詢基本上是一組如何擷取及組織數據的指示。 查詢會以惰性執行,因為僅在請求結果中的每個後續項目時才進行處理。 當您使用 foreach 來迭代結果時,項目會隨著存取而傳回。 若要評估查詢並儲存其結果而不執行 foreach 迴圈,只要在查詢變數上呼叫下列其中一個方法:

當您儲存查詢結果時,應該將傳回的集合物件指派給新的變數,如下列範例所示:

List<int> numbers = [ 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ];

IEnumerable<int> queryFactorsOfFour = from num in numbers
                                      where num % 4 == 0
                                      select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

另請參閱