C#3.0新增功能07 查詢表達式

  • 2019 年 10 月 4 日
  • 筆記

查詢是什麼及其作用是什麼

查詢是一組指令,描述要從給定數據源(或源)檢索的數據以及返回的數據應具有的形狀和組織。 查詢與它生成的結果不同。

通常情況下,源數據按邏輯方式組織為相同類型的元素的序列。 例如,SQL 資料庫表包含行的序列。 在 XML 文件中,存在 XML 元素的「序列」(儘管這些元素在樹結構按層次結構進行組織)。 記憶體中集合包含對象的序列。

從應用程式的角度來看,原始源數據的特定類型和結構並不重要。 應用程式始終將源數據視為 IEnumerable<T>IQueryable<T> 集合。 例如在 LINQ to XML 中,源數據顯示為 IEnumerable<XElement>。

對於此源序列,查詢可能會執行三種操作之一:

  • 檢索元素的子集以生成新序列,而不修改各個元素。 查詢然後可能以各種方式對返回的序列進行排序或分組,如下面的示例所示(假定 scoresint[]):
IEnumerable<int> highScoresQuery =      from score in scores      where score > 80      orderby score descending      select score;
  • 如前面的示例所示檢索元素的序列,但是將它們轉換為新類型的對象。 例如,查詢可以只從數據源中的某些客戶記錄檢索姓氏。 或者可以檢索完整記錄,然後用於構造其他記憶體中對象類型甚至是 XML 數據,再生成最終的結果序列。 下面的示例演示從 intstring 的投影。 請注意 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 子句開頭,且必須以 selectgroup 子句結尾。

(2)在第一個 from 子句與最後一個 selectgroup 子句之間,可以包含以下這些可選子句中的一個或多個:whereorderbyjoinlet,甚至是其他 from 子句。 還可以使用 into 關鍵字,使 joingroup 子句的結果可以充當相同查詢表達式中的其他查詢子句的源。

查詢變數

在 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 循環中進行循環訪問。 只要既沒有修改它,也沒有修改數據源,便會生成相同結果。

查詢變數可以存儲採用查詢語法、方法語法或是兩者的組合進行表示的查詢。 在以下示例中,queryMajorCitiesqueryMajorCities2 都是查詢變數:

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 子句開頭。 它指定數據源以及範圍變數。 範圍變數表示遍歷源序列時,源序列中的每個連續元素。 範圍變數基於數據源中元素的類型進行強類型化。 在下面的示例中,因為 countriesCountry 對象的數組,所以範圍變數也類型化為 Country。 因為範圍變數是強類型,所以可以使用點運算符訪問該類型的任何可用成員。

IEnumerable<Country> countryAreaQuery =      from country in countries      where country.Area > 500000 //面積大於500000      select country;

範圍變數一直處於範圍中,直到查詢使用分號或 continuation 子句退出。

查詢表達式可能會包含多個 from 子句。 在源序列中的每個元素本身是集合或包含集合時,可使用其他 from 子句。 例如,假設具有 Country 對象的集合,其中每個對象都包含名為 CitiesCity 對象集合。 若要查詢每個 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」進行延續

可以在 selectgroup 子句中使用 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 子句與結尾 selectgroup 子句之間,所有其他子句(wherejoinorderbyfromlet)都是可選的。 任何可選子句都可以在查詢正文中使用零次或多次。

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 中,聯接操作是對元素屬於不同類型的對象序列執行。 聯接了兩個序列之後,必須使用 selectgroup 語句指定要存儲在輸出序列中的元素。 還可以使用匿名類型將每組關聯元素中的屬性合併到輸出序列的新類型中。下面的示例關聯其 Category 屬性與 categories 字元串數組中一個類別匹配的 prod 對象。篩選出其 Category 不與 categories 中的任何字元串匹配的產品。select 語句會投影其屬性取自 catprod 的新類型。

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