次の方法で共有


クエリ式の基本

この記事では、C# のクエリ式に関連する基本的な概念について説明します。

クエリとは何ですか。また、何が行われますか?

クエリ は、特定のデータ ソース (またはソース) から取得するデータと、返されるデータの形状と編成について説明する一連の命令です。 クエリは、生成される結果とは異なります。

一般に、ソース データは、同じ種類の要素のシーケンスとして論理的に編成されます。 たとえば、SQL データベース テーブルには一連の行が含まれています。 XML ファイルには、XML 要素の "シーケンス" があります (ただし、XML 要素はツリー構造で階層的に編成されます)。 メモリ内コレクションには、一連のオブジェクトが含まれています。

アプリケーションの観点からは、元のソース データの特定の型と構造は重要ではありません。 アプリケーションは常にソース データを IEnumerable<T> または IQueryable<T> コレクションとして見なします。 たとえば、LINQ to XML では、ソース データは IEnumerable<XElement>として表示されます。

このソース シーケンスを考えると、クエリによって次の 3 つのいずれかが実行される場合があります。

  • 要素のサブセットを取得して、個々の要素を変更せずに新しいシーケンスを生成します。 クエリでは、次の例に示すように、返されたシーケンスをさまざまな方法で並べ替えたりグループ化したりできます (scoresint[]であると仮定します)。

    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# 式が含まれており、これらの式自体がクエリ式であるか、クエリ式を含む場合があります。

クエリ式は、 句の で始まり、select 句またはグループ 句で終わる必要があります。 最初の from 句と最後の select または group 句の間には、次の省略可能句を 1 つ以上含めることができます: whereorderbyjoinlet、および別の 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 つの組み合わせが格納される場合があります。 次の例では、queryMajorCitiesqueryMajorCities2 の両方がクエリ変数です。

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 句で始まる必要があります。 範囲変数と共にデータ ソースを指定します。 範囲変数は、ソース シーケンスが走査されるときに、ソース シーケンス内の連続する各要素を表します。 範囲変数は、データ ソース内の要素の型に基づいて厳密に型指定されます。 次の例では、countriesCountry オブジェクトの配列であるため、範囲変数も Countryとして型指定されます。 範囲変数は厳密に型指定されているため、ドット演算子を使用して、その型の使用可能なメンバーにアクセスできます。

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 20 //sq km
    select country;

範囲変数は、クエリがセミコロンまたは 継続 句のいずれかで終了するまで、範囲内にあります。

クエリ式には、複数の from 句が含まれる場合があります。 ソース シーケンス内の各要素がそれ自体がコレクションであるか、コレクションが含まれている場合は、より多くの from 句を使用します。 たとえば、Country オブジェクトのコレクションがあり、それぞれに Citiesという名前の City オブジェクトのコレクションが含まれているとします。 各 CountryCity オブジェクトに対してクエリを実行するには、次に示すように 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 句の間では、他のすべての句 (wherejoinorderbyfromlet) は省略可能です。 任意の句は、クエリ本文で 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 ステートメントは、プロパティが 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 操作の結果を一時変数に格納することで、グループ結合を実行することもできます。 詳細については、結合句を参照してください。

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

詳細については、「グループ化操作でサブクエリを実行する」を参照してください。

関連項目