LINQ クエリの概要 (C#)
クエリとは、データ ソースからデータを取得するための式です。 クエリは通常、専用のクエリ言語で表されます。 これまでに、リレーショナル データベース用の SQL や XML 用の XQuery など、データ ソースの種類に合わせてさまざまな言語が開発されてきました。 このため、開発者は、サポートする必要のあるデータ ソースの種類やデータ形式ごとに、新しいクエリ言語を習得する必要がありました。 LINQ は、さまざまな種類のデータ ソースやデータ形式のデータを操作するための一貫したモデルを提供することにより、この負担を軽減します。 LINQ クエリでは、操作の対象は常にオブジェクトになります。 共通の基本的なコーディング パターンを使用することで、XML ドキュメント、SQL データベース、ADO.NET データセット、.NET コレクション、および LINQ プロバイダーを利用できる他の任意の形式のデータを照会したり変換したりできます。
クエリ操作の 3 つの手順
すべての LINQ クエリ操作は、次の 3 つの手順で構成されます。
データ ソースを取得します。
クエリを作成します。
クエリを実行します。
クエリ操作の 3 つの手順がソース コードでどのように表されるかを次の例に示します。 この例では、わかりやすくするために整数の配列をデータ ソースとして使用していますが、他のデータ ソースを使用する場合にも同じ概念が当てはまります。 このコードは、このトピックの残りの部分全体を通して参照されます。
class IntroToLINQ
{
static void Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}
次の図は、クエリ操作全体を表しています。 LINQ では、クエリの実行はクエリ自体とは別個のものです。つまり、クエリ変数を作成するだけでは、データは取得されません。
データ ソース
前の例では、データ ソースが配列であるため、暗黙的にジェネリック IEnumerable<T> インターフェイスがサポートされます。 つまり、LINQ でクエリを実行できるということです。 クエリは foreach ステートメントで実行されますが、foreach には IEnumerable または IEnumerable<T> が必要です。IEnumerable<T> をサポートする型や、ジェネリック IQueryable<T> などの派生インターフェイスは、クエリ可能型と呼ばれます。
クエリ可能型は、変更や特別な処理を行わなくても、LINQ データ ソースとして使用できます。 ソース データがメモリ内にクエリ可能型として存在していない場合、LINQ プロバイダーは、そのような型としてソース データを表す必要があります。 たとえば、LINQ to XML では、クエリ可能な XElement 型に XML ドキュメントが読み込まれます。
// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");
LINQ to SQL では、まず、デザイン時に手動で、または オブジェクト リレーショナル デザイナー (O/R デザイナー) を使用して、オブジェクト リレーショナル マッピングを作成します。 オブジェクトに対するクエリを記述すると、実行時には、LINQ to SQL によってデータベースとの通信が処理されます。 次の例では、Customers がデータベース内の特定のテーブルを表し、クエリ結果の型 IQueryable<T> が IEnumerable<T> から派生しています。
Northwnd db = new Northwnd(@"c:\northwnd.mdf");
// Query for customers in London.
IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;
それぞれの種類のデータ ソースを作成する方法の詳細については、対応する LINQ プロバイダーのドキュメントを参照してください。 ただし、基本的な規則は非常に単純です。LINQ データ ソースは、ジェネリック IEnumerable<T> インターフェイス、またはこれを継承するインターフェイスをサポートする任意のオブジェクトです。
注意
非ジェネリック IEnumerable インターフェイスをサポートする ArrayList などの型も、LINQ データ ソースとして使用できます。 詳細については、「方法 : LINQ を使用して ArrayList を照会する」を参照してください。
クエリ
クエリでは、データ ソースからどのような情報を取得するかを指定します。 オプションとして、情報が返される前に、その情報を並べ替え、グループ化し、構造化する方法を指定することもできます。 クエリはクエリ変数に格納され、クエリ式で初期化されます。 クエリを簡単に記述できるようにするために、C# に新しいクエリ構文が導入されています。
前の例のクエリでは、整数の配列からすべての偶数が返されます。 クエリ式には、from、where、および select の 3 つの句が含まれています (SQL に詳しい方は、句の順番が SQL での順番とは逆になっていることに気付かれると思います)。 from 句はデータ ソースを指定し、where 句はフィルターを適用し、select 句は返される要素の種類を指定します。 これらのクエリ句およびその他のクエリ句の詳細については、「LINQ クエリ式 (C# プログラミング ガイド)」で説明しています。 今の段階で重要な点は、LINQ では、クエリ変数自体は何も処理を行わず、データを返さないという点です。 この時点では、後でクエリが実行されるときに結果の生成に必要となる情報が格納されるだけです。 背後でどのようにクエリが構築されるかについては、「標準クエリ演算子の概要」を参照してください。
注意
クエリは、メソッド構文を使用して表すこともできます。 詳細については、「LINQ クエリ構文とメソッド構文 (C#)」を参照してください。
クエリの実行
遅延実行
先に説明したように、クエリ変数自体が行うのはクエリ コマンドの格納のみです。 実際のクエリの実行は、foreach ステートメントでクエリ変数が反復処理されるまで延期されます。 この概念を遅延実行と呼びます。遅延実行の例を次に示します。
// Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
foreach ステートメントでは、クエリ結果の取得も行われます。 たとえば、前のクエリでは、返されるシーケンスの各値が反復変数 num に (一度に 1 つずつ) 格納されます。
クエリ変数自体にはクエリ結果は格納されないので、必要に応じて何度でもクエリを実行できます。 たとえば、別のアプリケーションによって頻繁に更新されるデータベースがあるとします。 自分のアプリケーションでは、最新のデータを取得するクエリを 1 つ作成し、それを一定の間隔で繰り返し実行することで、毎回異なる結果を取得できます。
即時実行の強制
一連のソース要素に対して集計関数を実行するクエリでは、最初にそれらの要素を反復処理する必要があります。 このようなクエリには、Count、Max、Average、First などがあります。 これらのクエリでは、明示的に foreach ステートメントを使用しなくても同等の処理が実行されます。これは、結果を返すためにクエリ自体が foreach を使用する必要があるからです。 これらの種類のクエリでは、IEnumerable コレクションではなく、単一の値が返されることにも注意してください。 次のクエリは、ソース配列に含まれている偶数の数を返します。
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;
int evenNumCount = evenNumQuery.Count();
クエリの即時実行を強制し、その結果をキャッシュするには、ToList<TSource> メソッドまたは ToArray<TSource> メソッドを呼び出します。
List<int> numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();
// or like this:
// numQuery3 is still an int[]
var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();
クエリ式の直後に foreach ループを配置することでも実行を強制できます。 ただし、ToList または ToArray を呼び出した場合は、単一のコレクション オブジェクトにすべてのデータをキャッシュする処理も行われます。