事件
C# 的 LINQ 查詢簡介
「查詢」是指從資料來源中擷取資料的運算式。 不同的資料來源有不同的原生查詢語言,例如關聯式資料庫 SQL 和 XML 的 XQuery。 開發人員必須針對所需支援的每種資料來源類型或資料格式,學習新的查詢語言。 LINQ 為資料來源和格式類型提供一致的 C# 語言模型,以簡化這種情況。 在 LINQ 查詢中,您一律會使用 C# 物件。 您會使用相同的基本編碼模式,來查詢及轉換 XML 文件、SQL 資料庫、.NET 集合,以及當 LINQ 提供者可用時任何其他格式中的資料。
所有的 LINQ 查詢作業都包含三個不同的動作:
- 取得資料來源。
- 建立查詢。
- 執行查詢。
下列範例示範查詢作業的三個部分在原始程式碼中的表示方式。 為了方便起見,此範例使用整數陣列作為資料來源;不過,相同的概念也適用於其他資料來源。 本文章的其他部分都會參考此範例。
// 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 中,查詢的執行與查詢本身不同。 換句話說,您不會藉由建立查詢變數來擷取任何資料。
上述範例中的資料來源為陣列,可支援泛型 IEnumerable<T> 介面。 這表示可使用 LINQ 進行查詢。 查詢會在 foreach
陳述式中執行,而 foreach
則需要IEnumerable 或 IEnumerable<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 提供者的文件。 但基本的規則很清楚:任何支援泛型 IEnumerable<T> 介面或所繼承介面的物件,皆可為 LINQ 資料來源,通常是 IQueryable<T>。
備註
這些類型 (例如 ArrayList) 支援非泛型 IEnumerable 介面,也可作為 LINQ 資料來源。 如需詳細資訊,請參閱如何使用 LINQ 查詢 ArrayList (C#)。
查詢可指定要從一或多個資料來源擷取的資訊。 查詢也可選擇性地指定該項資訊傳回之前應該如何排序、分組和成形。 查詢是儲存在查詢變數中,並以查詢運算式初始化。 您可以使用 C# 查詢語法來撰寫查詢。
上述範例中的查詢會傳回整數陣列中的所有偶數。 此查詢運算式包含三個子句︰from
、where
和 select
。 (如果您熟悉 SQL,您注意到這些子句的排序與 SQL 中的排序相反。)from
子句會指定資料來源,where
子句會套用篩選,而 select
子句會指定傳回項目的類型。 本節會詳細討論所有查詢子句。 但目前的重點是,LINQ 中的查詢變數本身不會採取任何動作,且不會傳回任何資料。 它只會儲存稍後執行查詢以產生結果時所需要的資訊。 如需如何建構查詢的詳細資訊,請參閱標準查詢運算子概觀 (C#) (英文)。
備註
查詢也可以使用方法語法來表示。 如需詳細資訊,請參閱 LINQ 中的查詢語法及方法語法。
標準查詢運算子方法的 LINQ to Objects 實作會以兩種主要方式之一執行:立即或延後。 使用延後執行的查詢運算子可以另外細分成兩個分類︰資料流和非資料流。
立即執行表示已讀取資料來源,並執行作業一次。 所有傳回純量結果的標準查詢運算子都會立即執行。 這類查詢的範例包括 Count
、Max
、Average
和 First
。 這些方法執行時並未使用明確的 foreach
陳述式,因為查詢本身必須使用 foreach
才能傳回結果。 這些查詢傳回的是單一的值,而不是 IEnumerable
集合。 您可以使用 Enumerable.ToList 或 Enumerable.ToArray 方法來強制任何查詢立即執行。 立即執行可讓您重複使用查詢結果,而不是查詢宣告。 結果會擷取一次,然後加以儲存以供日後使用。 下列查詢會傳回來源陣列中的偶數計數:
var evenNumQuery = from num in numbers
where (num % 2) == 0
select num;
int evenNumCount = evenNumQuery.Count();
若要立即強制執行任何查詢,並快取其結果,可以呼叫 ToList 或 ToArray方法。
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
迴圈放在緊接著查詢運算式後方的位置,以強制執行查詢。 不過,藉由呼叫 ToList
或 ToArray
,您也可以快取單一集合物件中的所有資料。
延後執行表示不會在程式碼中宣告查詢的位置執行作業。 只有在列舉查詢變數時,才會執行這項作業,例如,透過使用 foreach
陳述式。 執行查詢的結果取決於執行查詢時的資料來源內容,而不是定義查詢時的資料來源內容。 如果多次列舉查詢變數,則每次都可能會有不同的結果。 幾乎傳回型別為 IEnumerable<T> 或 IOrderedEnumerable<TElement> 的所有標準查詢運算子都會以延遲方式執行。 延遲的執行可提供查詢重複使用的功能,因為每次重複傳回查詢結果時,查詢都會從資料來源擷取更新的資料。 下列程式碼示範延後執行的範例:
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
foreach
陳述式也是擷取查詢結果的位置。 例如,在上述查詢中,反覆運算變數 num
會保留傳回序列中的每個值 (一次一個)。
因為查詢變數本身絕不會保留查詢結果,所以您可以重複執行它以擷取更新的資料。 舉例來說,個別的應用程式可能會持續更新資料庫。 在您的應用程式中,您可以建立一個擷取最新資料的查詢,並且每隔一段時間執行一次,以擷取更新的結果。
使用延後執行的查詢運算子可以另外分類為資料流和非資料流。
資料流運算子在產生項目之前不需要讀取所有來源資料。 執行時,資料流運算子會在讀取並產生項目時 (適用時) 於每個來源項目上執行其運算。 除非產生結果項目,否則資料流運算子會繼續讀取來源項目。 這表示可能會讀取多個來源項目,以產生一個結果項目。
非資料流運算子在產生結果項目之前必須讀取所有來源資料。 排序或分組這類作業會歸到此分類。 執行時,非資料流查詢運算子會讀取所有來源資料、將其放入資料結構中、執行作業,並產生結果項目。
下表會根據執行方法來分類每個標準查詢運算子方法。
備註
如果在兩個資料行中標記運算子,則作業包含兩個輸入序列,而且會以不同的方式評估每個序列。 在這些情況下,它一律是參數清單中以延後資料流方式評估的第一個序列。
「LINQ to Objects」是指直接搭配使用 LINQ 查詢和任何 IEnumerable 或 IEnumerable<T> 集合。 您可以使用 LINQ 查詢任何可列舉的集合,例如 List<T>、Array 或 Dictionary<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]);