クエリ式の基本
この記事では、C# のクエリ式に関連する基本的な概念について説明します。
クエリとは何ですか。また、何が行われますか?
クエリ は、特定のデータ ソース (またはソース) から取得するデータと、返されるデータの形状と編成について説明する一連の命令です。 クエリは、生成される結果とは異なります。
一般に、ソース データは、同じ種類の要素のシーケンスとして論理的に編成されます。 たとえば、SQL データベース テーブルには一連の行が含まれています。 XML ファイルには、XML 要素の "シーケンス" があります (ただし、XML 要素はツリー構造で階層的に編成されます)。 メモリ内コレクションには、一連のオブジェクトが含まれています。
アプリケーションの観点からは、元のソース データの特定の型と構造は重要ではありません。 アプリケーションは常にソース データを IEnumerable<T> または IQueryable<T> コレクションとして見なします。 たとえば、LINQ to XML では、ソース データは IEnumerable
<XElement>として表示されます。
このソース シーケンスを考えると、クエリによって次の 3 つのいずれかが実行される場合があります。
要素のサブセットを取得して、個々の要素を変更せずに新しいシーケンスを生成します。 クエリでは、次の例に示すように、返されたシーケンスをさまざまな方法で並べ替えたりグループ化したりできます (
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 と同様の宣言構文で記述された句のセットで構成されます。 さらに、各句には 1 つ以上の C# 式が含まれており、これらの式自体がクエリ式であるか、クエリ式を含む場合があります。
クエリ式は、 句の from
句と最後の select
または group
句の間には、次の省略可能句を 1 つ以上含めることができます: where、orderby、join、let、および別の from 句。
クエリ変数
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)])
];
次のコード例は、1 つのデータ ソース、1 つのフィルター句、1 つの順序句、およびソース要素の変換がない単純なクエリ式を示しています。 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
変数は、2 つ目の foreach
ループで反復処理できます。 データ ソースも変更されていない限り、同じ結果が生成されます。
クエリ変数には、クエリ構文またはメソッド構文で表されるクエリ、または 2 つの組み合わせが格納される場合があります。 次の例では、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 }
一方、次の 2 つの例は、それぞれがクエリで初期化されている場合でも、クエリ変数ではない変数を示しています。 結果が格納されるため、これらはクエリ変数ではありません。
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
オブジェクトのコレクションがあり、それぞれに Cities
という名前の City
オブジェクトのコレクションが含まれているとします。 各 Country
の City
オブジェクトに対してクエリを実行するには、次に示すように 2 つの from
句を使用します。
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
詳細については、句の
クエリ式を終了する
クエリ式は、group
句または select
句で終わる必要があります。
group 句
group
句を使用して、指定したキーで編成されたグループのシーケンスを生成します。 キーには任意のデータ型を指定できます。 たとえば、次のクエリでは、1 つ以上の 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
句または group
句で into
キーワードを使用して、クエリを格納する一時識別子を作成できます。 グループ化または選択操作の後にクエリに対して追加のクエリ操作を実行する必要がある場合は、into
句を使用します。 次の例では、1 千万という範囲の人口で countries
をグループ化しています。 これらのグループが作成されると、より多くの句によっていくつかのグループが除外され、グループが昇順で並べ替えられます。 これらの追加操作を実行するには、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
) は省略可能です。 任意の句は、クエリ本文で 0 回または複数回使用できます。
where 句
where
句を使用して、1 つ以上の述語式に基づいてソース データから要素を除外します。 次の例の where
句には、2 つの条件を持つ 1 つの述語があります。
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
詳細については、「where 句」を参照してください。
orderby 句
orderby
句を使用して、結果を昇順または降順で並べ替えます。 第 2 の並べ替え順序を指定することもできます。 次の例では、Area
プロパティを使用して、country
オブジェクトに対してプライマリ並べ替えを実行します。 次に、Population
プロパティを使用してセカンダリ並べ替えを実行します。
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
ascending
キーワードは省略可能です。順序が指定されていない場合の既定の並べ替え順序です。 詳しくは、「orderby 句」をご覧ください。
join 句
join
句を使用して、1 つのデータ ソースの要素を、各要素内の指定されたキー間の等値比較に基づいて、別のデータ ソースの要素と関連付けたり結合したりします。 LINQ では、要素が異なる型のオブジェクトのシーケンスに対して結合操作が実行されます。 2 つのシーケンスを結合した後、select
または group
ステートメントを使用して、出力シーケンスに格納する要素を指定する必要があります。 匿名型を使用して、関連付けられている要素の各セットのプロパティを出力シーケンスの新しい型に結合することもできます。 次の例では、Category
プロパティが categories
文字列配列のいずれかのカテゴリに一致する prod
オブジェクトを関連付けます。 categories
内の文字列と一致しない Category
を持つ製品は除外されます。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
操作の結果を一時変数に格納することで、グループ結合を実行することもできます。 詳細については、結合句を参照してください。
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()
};
詳細については、「グループ化操作でサブクエリを実行する」を参照してください。
関連項目
.NET