C#3.0新增功能07 查詢表達式
- 2019 年 10 月 4 日
- 筆記
查詢是什麼及其作用是什麼
查詢是一組指令,描述要從給定數據源(或源)檢索的數據以及返回的數據應具有的形狀和組織。 查詢與它生成的結果不同。
通常情況下,源數據按邏輯方式組織為相同類型的元素的序列。 例如,SQL 資料庫表包含行的序列。 在 XML 文件中,存在 XML 元素的「序列」(儘管這些元素在樹結構按層次結構進行組織)。 記憶體中集合包含對象的序列。
從應用程式的角度來看,原始源數據的特定類型和結構並不重要。 應用程式始終將源數據視為 IEnumerable<T> 或 IQueryable<T> 集合。 例如在 LINQ to XML 中,源數據顯示為 IEnumerable
<XElement>。
對於此源序列,查詢可能會執行三種操作之一:
- 檢索元素的子集以生成新序列,而不修改各個元素。 查詢然後可能以各種方式對返回的序列進行排序或分組,如下面的示例所示(假定
scores
是int[]
):
IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
- 如前面的示例所示檢索元素的序列,但是將它們轉換為新類型的對象。 例如,查詢可以只從數據源中的某些客戶記錄檢索姓氏。 或者可以檢索完整記錄,然後用於構造其他記憶體中對象類型甚至是 XML 數據,再生成最終的結果序列。 下面的示例演示從
int
到string
的投影。 請注意highScoresQuery
的新類型。
IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
- 檢索有關源數據的單獨值,如:
- 與特定條件匹配的元素數。
- 具有最大或最小值的元素。
- 與某個條件匹配的第一個元素,或指定元素集中特定值的總和。 例如,下面的查詢從
scores
整數數組返回大於 80 的分數的數量:
int highScoreCount = (from score in scores where score > 80 select score) .Count();
在前面的示例中,請注意在調用 Count
方法之前,在查詢表達式兩邊使用了括弧。也可以通過使用新變數存儲具體結果,來表示此行為。 這種方法更具可讀性,因為它使存儲查詢的變數與存儲結果的查詢分開。
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; int scoreCount = highScoresQuery3.Count();
在上面的示例中,查詢在 Count
調用中執行,因為 Count
必須循環訪問結果才能確定 highScoresQuery
返回的元素數。
查詢表達式是什麼
查詢表達式是以查詢語法表示的查詢。 查詢表達式是一流的語言構造。 它如同任何其他表達式一樣,可以在 C# 表達式有效的任何上下文中使用。 查詢表達式由一組用類似於 SQL 或 XQuery 的聲明性語法所編寫的子句組成。 每個子句進而包含一個或多個 C# 表達式,而這些表達式可能本身是查詢表達式或包含查詢表達式。
(1)查詢表達式必須以 from 子句開頭,且必須以 select 或 group 子句結尾。
(2)在第一個 from
子句與最後一個 select
或 group
子句之間,可以包含以下這些可選子句中的一個或多個:where、orderby、join、let,甚至是其他 from 子句。 還可以使用 into 關鍵字,使 join
或 group
子句的結果可以充當相同查詢表達式中的其他查詢子句的源。
查詢變數
在 LINQ 中,查詢變數是存儲查詢而不是查詢結果的任何變數。 更具體地說,查詢變數始終是可枚舉類型,在 foreach
語句或對其 IEnumerator.MoveNext
方法的直接調用中循環訪問時會生成元素序列。
下面的程式碼示例演示一個簡單查詢表達式,它具有一個數據源、一個篩選子句、一個排序子句並且不轉換源元素。 該查詢以 select
子句結尾。
static void Main() { // 數據源 int[] scores = { 90, 71, 82, 93, 75, 82 }; // 查詢表達式 IEnumerable<int> scoreQuery = // 查詢變數 from score in scores // 必須 where score > 80 // 可選 orderby score descending // 可選 select score; // 必須以 select 或者 group 結尾 // 執行查詢併產生結果 foreach (int testScore in scoreQuery) { Console.WriteLine(testScore); } } // 輸出: 93 90 82 82
在上面的示例中,scoreQuery
是查詢變數,它有時僅僅稱為查詢。 查詢變數不存儲在 foreach
循環生成中的任何實際結果數據。 並且當 foreach
語句執行時,查詢結果不會通過查詢變數 scoreQuery
返回。 而是通過迭代變數 testScore
返回。 scoreQuery
變數可以在另一個 foreach
循環中進行循環訪問。 只要既沒有修改它,也沒有修改數據源,便會生成相同結果。
查詢變數可以存儲採用查詢語法、方法語法或是兩者的組合進行表示的查詢。 在以下示例中,queryMajorCities
和 queryMajorCities2
都是查詢變數:
var cities = new City { new city(){Name = "上海",Population = 24180000}, new city(){Name = "南京",Population = 8436200}, new city(){Name = "北京",Population = 21710000}, new city(){Name = "廣州",Population = 14900000} }; // 查詢語法 IEnumerable<City> queryMajorCities = from city in cities where city.Population > 100000 select city; // 基於方法的語法 IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);
另一方面,以下兩個示例演示不是查詢變數的變數(即使各自使用查詢進行初始化)。 它們不是查詢變數,因為它們存儲結果:
int highestScore = (from score in scores select score) .Max(); // 或者拆分表達式 IEnumerable<int> scoreQuery = from score in scores select score; int highScore = scoreQuery.Max(); // 下面的表達式返回相同的結果 int highScore = scores.Max(); List<City> largeCitiesList = (from country in countries from city in country.Cities where city.Population > 10000 select city) .ToList(); // 或者拆分表達式 IEnumerable<City> largeCitiesQuery = from country in countries from city in country.Cities where city.Population > 10000 select city; List<City> largeCitiesList2 = largeCitiesQuery.ToList();
有關表示查詢的不同方式的詳細資訊,請參閱 LINQ 中的查詢語法和方法語法。
查詢變數的顯式和隱式類型化
本文檔通常提供查詢變數的顯式類型以便顯示查詢變數與 select 子句之間的類型關係。 但是,還可以使用 var 關鍵字指示編譯器在編譯時推斷查詢變數(或任何其他局部變數)的類型。 例如,本主題中前面演示的查詢示例也可以使用隱式類型化進行表示:
// 在這裡和所有查詢中使用var都是可選的。querycities是一個IEnumerable<city>就像它是顯式類型一樣 var queryCities = from city in cities where city.Population > 100000 select city;
有關詳細資訊,請參閱隱式類型化局部變數和 LINQ 查詢操作中的類型關係。
開始查詢表達式
查詢表達式必須以 from
子句開頭。 它指定數據源以及範圍變數。 範圍變數表示遍歷源序列時,源序列中的每個連續元素。 範圍變數基於數據源中元素的類型進行強類型化。 在下面的示例中,因為 countries
是 Country
對象的數組,所以範圍變數也類型化為 Country
。 因為範圍變數是強類型,所以可以使用點運算符訪問該類型的任何可用成員。
IEnumerable<Country> countryAreaQuery = from country in countries where country.Area > 500000 //面積大於500000 select country;
範圍變數一直處於範圍中,直到查詢使用分號或 continuation 子句退出。
查詢表達式可能會包含多個 from
子句。 在源序列中的每個元素本身是集合或包含集合時,可使用其他 from
子句。 例如,假設具有 Country
對象的集合,其中每個對象都包含名為 Cities
的 City
對象集合。 若要查詢每個 Country
中的 City
對象,請使用兩個 from
子句,如下所示:
IEnumerable<City> cityQuery = from country in countries from city in country.Cities where city.Population > 10000 select city;
有關詳細資訊,請參閱 from 子句。
結束查詢表達式
查詢表達式必須以 group
子句或 select
子句結尾。
group 子句
使用 group
子句可生成按指定鍵組織的組的序列。 鍵可以是任何數據類型。 例如,下面的查詢會創建包含一個或多個 Country
對象並且其鍵是 char
值的組的序列。
var queryCountryGroups = from country in countries group country by country.Name[0];
有關分組的詳細資訊,請參閱 group 子句。
select 子句
使用 select
子句可生成所有其他類型的序列。 簡單 select
子句只生成類型與數據源中包含的對象相同的對象的序列。 在此示例中,數據源包含 Country
對象。 orderby
子句只按新順序對元素進行排序,而 select
子句生成重新排序的 Country
對象的序列。
IEnumerable<Country> sortedQuery = from country in countries orderby country.Area select country;
select
子句可以用於將源數據轉換為新類型的序列。 此轉換也稱為投影。 在下面的示例中,select
子句對只包含原始元素中的欄位子集的匿名類型序列進行投影。 請注意,新對象使用對象初始值設定項進行初始化。
// 此處 var 是必須的,因為查詢返回了匿名類型 var queryNameAndPop = from country in countries select new { Name = country.Name, Pop = country.Population };
有關可以使用 select
子句轉換源數據的所有方法的詳細資訊,請參閱 select 子句。
使用「into」進行延續
可以在 select
或 group
子句中使用 into
關鍵字創建存儲查詢的臨時標識符。 如果在分組或選擇操作之後必須對查詢執行其他查詢操作,則可以這樣做。 在下面的示例中,countries
按 1000 萬範圍,根據人口進行分組。 創建這些組之後,附加子句會篩選出一些組,然後按升序對組進行排序。 若要執行這些附加操作,需要由 countryGroup
表示的延續。
// 該查詢返回的類型是 IEnumerable<IGrouping<int, Country>> var percentileQuery = from country in countries let percentile = (int) country.Population / 10_000_000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; // 分組是 IGrouping<int, Country> foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) Console.WriteLine(country.Name + ":" + country.Population); }
有關詳細資訊,請參閱 into。
篩選、排序和聯接
在開頭 from
子句與結尾 select
或 group
子句之間,所有其他子句(where
、join
、orderby
、from
、let
)都是可選的。 任何可選子句都可以在查詢正文中使用零次或多次。
IEnumerable<City> queryCityPop = from city in cities where city.Population < 200000 && city.Population > 100000 select city;
有關詳細資訊,請參閱 where 子句。
orderby 子句
使用 orderby
子句可按升序或降序對結果進行排序。 還可以指定次要排序順序。 下面的示例使用 Area
屬性對 country
對象執行主要排序。 然後使用 Population
屬性執行次要排序。
IEnumerable<Country> querySortedCountries = from country in countries orderby country.Area, country.Population descending select country;
ascending
關鍵字是可選的;如果未指定任何順序,則它是默認排序順序。 有關詳細資訊,請參閱 orderby 子句。
join 子句
使用 join
子句可基於每個元素中指定的鍵之間的相等比較,將一個數據源中的元素與另一個數據源中的元素進行關聯和/或合併。 在 LINQ 中,聯接操作是對元素屬於不同類型的對象序列執行。 聯接了兩個序列之後,必須使用 select
或 group
語句指定要存儲在輸出序列中的元素。 還可以使用匿名類型將每組關聯元素中的屬性合併到輸出序列的新類型中。下面的示例關聯其 Category
屬性與 categories
字元串數組中一個類別匹配的 prod
對象。篩選出其 Category
不與 categories
中的任何字元串匹配的產品。select
語句會投影其屬性取自 cat
和 prod
的新類型。
var categoryQuery = from cat in categories join prod in products on cat equals prod.Category select new { Category = cat, Name = prod.Name };
還可以通過使用 into 關鍵字將 join
操作的結果存儲到臨時變數中來執行分組聯接。 有關詳細資訊,請參閱 join 子句。
let 子句
使用 let
子句可將表達式(如方法調用)的結果存儲在新範圍變數中。 在下面的示例中,範圍變數 firstName
存儲 Split
返回的字元串數組的第一個元素。
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" }; IEnumerable<string> queryFirstNames = from name in names let firstName = name.Split(' ')[0] select firstName; foreach (string s in queryFirstNames) Console.Write(s + " ");
有關詳細資訊,請參閱 let 子句。
查詢表達式中的子查詢
查詢子句本身可能包含查詢表達式,這有時稱為子查詢。 每個子查詢都以自己的 from
子句開頭,該子句不一定指向第一個 from
子句中的相同數據源。 例如,下面的查詢演示在 select 語句用於檢索分組操作結果的查詢表達式。
var queryGroupMax = from student in students group student by student.GradeLevel into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.Scores.Average()) .Max() };