查詢表達式基本概念
本文介紹 C# 中查詢表達式的相關基本概念。
什麼是查詢及其用途?
查詢 是一組指示,說明要從指定數據源(或來源)擷取哪些數據,以及傳回的數據應該具有何種形狀和組織。 查詢與它所產生的結果不同。
一般而言,源數據會以邏輯方式組織為相同種類的元素序列。 例如,SQL 資料庫數據表包含一連串的數據列。 在 XML 檔案中,有 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的分數數目:var highScoreCount = ( from score in scores where score > 80 select score ).Count();
在上一個範例中,請注意在呼叫 Enumerable.Count 方法之前,在查詢表達式周圍使用括號。 您也可以使用新的變數來儲存具體結果。
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
在上一個範例中,查詢會在呼叫 Count
中執行,因為 Count
必須逐一查看結果,才能判斷 highScoresQuery
所傳回的項目數。
什麼是查詢表達式?
查詢表示式 是以查詢語法表示的查詢。 查詢表達式是一流的語言建構。 就像任何其他表達式一樣,而且可用於 C# 運算式有效的任何內容中。 查詢表達式是由一組以類似 SQL 或 XQuery 的宣告式語法所撰寫的子句所組成。 每個子句依次包含一或多個 C# 運算式,這些運算式本身可能是查詢運算式或包含查詢運算式。
查詢表達式必須以 子句中的 into
關鍵詞來啟用 join
或 group
子句的結果,做為相同查詢表達式中更多查詢子句的來源。
查詢變數
在 LINQ 中,查詢變數是任何儲存 查詢 而不是 查詢 結果的變數。 更具體來說,查詢變數一律是可枚舉的類型,可在逐一查看 foreach
語句或直接呼叫其 IEnumerator.MoveNext() 方法時生成元素序列。
注意
本文中的範例會使用下列數據源和範例數據。
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
下列程式代碼範例顯示具有一個數據源、一個篩選子句、一個排序子句,以及沒有來源元素轉換的簡單查詢表達式。
select
子句會結束查詢。
// Data source.
int[] scores = [90, 71, 82, 93, 75, 82];
// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group
// Execute the query to produce the results
foreach (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
// Output: 93 90 82 82
在上一個範例中,scoreQuery
是 查詢變數, 有時僅被稱為 查詢。 查詢變數不會儲存實際的結果數據,這會在 foreach
迴圈中產生。 而且當 foreach
語句執行時,查詢結果不會透過查詢變數傳回 scoreQuery
。 相反地,它們會通過迭代變數 testScore
傳回。
scoreQuery
變數可以在第二個 foreach
迴圈中進行迭代。 只要它和數據源都未修改,就會產生相同的結果。
查詢變數可能會儲存以查詢語法或方法語法表示的查詢,或兩者的組合。 在下列範例中,queryMajorCities
和 queryMajorCities2
都是查詢變數:
City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 30_000_000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 30_000_000);
// Execute the query to produce the results
foreach (City city in queryMajorCities2)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
另一方面,下列兩個範例顯示的是,即使每個變數都是使用查詢初始化,這些變數仍然不是查詢變數。 它們不是查詢變數,因為它們會儲存結果:
var highestScore = (
from score in scores
select score
).Max();
// or split the expression
IEnumerable<int> scoreQuery =
from score in scores
select score;
var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var largeCitiesList = (
from country in countries
from city in country.Cities
where city.Population > 10000
select city
).ToList();
// or split the expression
IEnumerable<City> largeCitiesQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
var largeCitiesList2 = largeCitiesQuery.ToList();
明確和隱含輸入查詢變數
本檔通常會提供查詢變數的明確類型,以顯示查詢變數與 select 子句之間的類型關聯性。 不過,您也可以使用 var 關鍵詞,指示編譯程式在編譯時期推斷查詢變數的類型(或任何其他局部變數)。 例如,本文中前面提到的查詢範例也可以使用隱含型別來表示。
var queryCities =
from city in cities
where city.Population > 100000
select city;
在上述範例中,var 的使用是選擇性的。
queryCities
是隱含或明確型別的 IEnumerable<City>
。
啟動查詢表達式
查詢表達式的開頭必須是 from
子句。 它會指定數據源與範圍變數。 範圍變數代表來源序列中的每個連續的元素,當來源序列正在被遍歷時。 範圍變數會根據數據源中的元素類型進行強類型化。 在下列範例中,由於 countries
是 Country
對象的陣列,因此範圍變數也會輸入為 Country
。 因為範圍變數是強類型,所以您可以使用點運算符來存取該類型的任何可用成員。
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
範圍變數處於作用域內,直到查詢以分號或 接續 子句結束為止。
查詢表達式可能包含多個 from
子句。 當來源序列中的每個元素本身是集合或包含集合時,請多使用 from
子句。 例如,假設您有一個 Country
物件的集合,而每個物件都包含一個名為 City
的 Cities
物件集合。 若要查詢每個 City
中的 Country
物件,請使用兩個 from
子句,如下所示:
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
如需詳細資訊,請參閱子句中的
結束查詢表達式
查詢表達式的結尾必須是 group
子句或 select
子句。
群組子句
使用 group
子句,產生由您指定的鍵值所組織的群組序列。 索引鍵可以是任何數據類型。 例如,下列查詢會建立一連串的群組,其中包含一或多個 Country
物件,且其索引鍵是 char
類型,其值為國家/地區名稱的第一個字母。
var queryCountryGroups =
from country in countries
group country by country.Name[0];
如需有關群組條款的詳細資訊,請參閱第 條款。
select 子句
使用 select
子句來產生所有其他序列類型。 簡單的 select
子句只會產生與數據源中包含的物件相同類型的物件序列。 在此範例中,數據源包含 Country
物件。
orderby
子句只會將元素按新的順序排序,而 select
子句會生成重新排序的 Country
物件序列。
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
select
子句可用來將源數據轉換成新類型的序列。 此轉換也稱為 投影。 在下列範例中,select
子句 專案 只包含原始專案中字段子集的匿名型別序列。 新物件通過使用物件初始化器來初始化。
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
因此,在此範例中,需要 var
,因為查詢會產生匿名類型。
如需有關 select
子句可以用來轉換來源資料的所有方式的詳細資訊,請參閱 select 子句。
將 內容延續到
您可以使用 into
或 select
子句中的 group
關鍵詞來建立儲存查詢的暫存標識符。 當您必須在群組或選取作業之後對查詢執行額外的查詢作業時,請使用 into
子句。 在下列範例中,countries
會根據人口以每1000萬一組進行分組。 建立這些群組之後,並使用更多子句來篩選某些群組,然後將群組進行遞增排序。 若要執行這些額外作業,需要以 countryGroup
表示的接續。
// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
from country in countries
let percentile = (int)country.Population / 1_000
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;
// grouping is an 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
) 都是選擇性的。 任何選擇性子句都可以在查詢主體中使用零次或多次。
where 子句
使用 where
子句,根據一或多個述詞表達式篩選出源數據中的元素。 在下列範例中,where
子句包含一個述词,并具有两个条件。
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
如需詳細資訊,請參閱 where 子句。
排序子句
使用 orderby
子句,以遞增或遞減順序排序結果。 您也可以指定次要排序順序。 下列範例會使用 country
屬性,在 Area
對象上執行主要排序。 然後,它會使用 Population
屬性來執行次要排序。
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
ascending
關鍵詞是選擇性的;如果未指定任何順序,則為預設排序順序。 如需詳細資訊,請參閱 排序子句。
join 子句
使用 join
子句,根據每個元素中指定索引鍵之間的相等比較,將一個數據來源中的元素與另一個數據來源中的元素關聯或結合。 在 LINQ 中,聯結作業會在元素為不同類型的物件序列上執行。 將兩個序列聯結後,您必須使用 select
或 group
敘述來指定要儲存在輸出序列中的元素。 您也可以使用匿名類型,將每個相關聯元素集的屬性結合成輸出序列的新類型。 下列範例會關聯 prod
物件的 Category
屬性符合 categories
字串陣列中的其中一個類別。 篩除 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 (var s in queryFirstNames)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
如需詳細資訊,請參閱 let 子句。
查詢表達式中的子查詢
查詢子句本身可能包含查詢表示式,有時稱為 子查詢。 每個子查詢都會以它自己的 from
子句開頭,該子句不一定指向第一個 from
子句中的相同數據源。 例如,下列查詢會顯示 select 語句中用來擷取群組作業結果的查詢表達式。
var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};