LINQとは何か
LINQ(言語統合クエリ、Language Integrated Query)は、.NETフレームワークの強力な拡張機能です。これにより、データベースを照会するのと同様の感覚で、オブジェクトのコレクション、XMLドキュメント、データベースなど、様々なデータソースをクエリできるようになります。LINQを使用すると、データ操作のコードがより読みやすく、簡潔になります。
LINQプロバイダ
異なる種類のデータソースに対してLINQクエリを実行するには、そのデータソースに特化したコードモジュール、いわゆるLINQプロバイダが必要です。Microsoftは、一般的なデータソース向けにいくつかのLINQプロバイダを提供しています。
| Visual C# 標準LINQサポート | ADO.NET LINQサポート |
|---|---|
| LINQ to Objects | LINQ to SQL |
| LINQ to XML | LINQ to Datasets |
| BLINQ (ASP.NET) | LINQ to Entities |
匿名型
匿名型は、名前のないクラス型を簡単に作成できる機能です。主にLINQクエリの結果を保持するために使用されます。匿名型の変数を作成するには、クラス名とコンストラクタを省略した形式を使用します。
new { プロパティ名 = 値, プロパティ名 = 値, ... };
以下は、匿名型の変数を作成し、そのプロパティにアクセスする例です。
static void Main(string[] args)
{
var product = new { Name = "Super Gadget", Price = 299.99, Category = "Electronics" };
Console.WriteLine($"{product.Name}, {product.Price}, {product.Category}");
}
注意点:
- 匿名型はローカル変数でのみ使用できます。クラスのメンバーとして使用することはできません。
- 匿名型には名前がないため、
varキーワードを使用する必要があります。 - 匿名型のオブジェクトのプロパティは読み取り専用です。コンパイラは、匿名型のオブジェクト初期化子を検出すると、プライベートな名前を持つ新しいクラス型を生成し、各メンバー初期化子の型を推論して、同じ名前を持つ読み取り専用プロパティを作成します。
クエリ構文とメソッド構文
LINQには、クエリを記述する2つの主な方法があります。
- クエリ構文: SQLに似た宣言的な構文です。何を取得したいかを記述しますが、どのように実行するかは指定しません。可読性が高く、複雑なクエリに適しています。
- メソッド構文: 標準クエリ演算子を使用した命令的な構文です。メソッド呼び出しの順序を明示的に指定します。
両方の構文は組み合わせて使用でき、実行時のパフォーマンスに差はありません。Microsoftはクエリ構文を推奨しています。
static void Main(string[] args)
{
int[] data = { 2, 5, 28, 31, 17, 16, 42 };
// クエリ構文
var filteredDataQuery = from d in data
where d < 20
select d;
// メソッド構文
var filteredDataMethod = data.Where(d => d < 20);
// 両方の構文を組み合わせる
int count = (from d in data
where d < 20
select d).Count();
foreach (var item in filteredDataQuery)
{
Console.WriteLine($"{item} ");
} // 2 5 17 16
Console.WriteLine();
foreach (var item in filteredDataMethod)
{
Console.WriteLine($"{item} ");
} // 2 5 17 16
Console.WriteLine(count); // 4
}
クエリ変数
LINQクエリは、2種類の結果を返すことができます。
- 列挙可能なコレクション: クエリ条件を満たす項目のリストです。この場合、クエリは列挙子が処理されるまで実行されません(遅延実行)。
- スカラー値: クエリ結果の要約(例:合計、平均、カウント)です。この場合、クエリはすぐに実行され、結果がクエリ変数に格納されます。
int[] values = { 2, 5, 28 };
// 列挙可能なコレクションを返すクエリ
IEnumerable<int> lowValues = from v in values where v < 20 select v;
// スカラー値を返すクエリ
int totalCount = (from v in values where v < 20 select v).Count();
クエリ変数(上記のlowValues)は、クエリの結果を直接保持しません。代わりに、クエリを実行するコードを生成します。クエリの実行タイミングは以下のようになります。
- 列挙可能なコレクションを返すクエリは、列挙子が処理されるまで実行されません。
- 列挙子が複数回処理されると、クエリも複数回実行されます。
- 列挙が開始される前にデータが変更されると、新しいデータが使用されます。
- スカラー値を返すクエリは、すぐに実行され、結果がクエリ変数に格納されます。
クエリ式の構造
LINQクエリ式は、特定の順序で句を記述する必要があります。
from句とselect...group句は必須です。- 他の句(
where,orderbyなど)はオプションです。 select句は式の最後に記述します。- 任意の数の
from...let...where句を含めることができます。
from句
クエリのデータソースを指定し、反復変数を導入します。
from 型 反復変数 in コレクション
反復変数は、データソースの各要素を表します。型はコンパイラが推論できるため、省略可能です。
int[] arr1 = { 10, 11, 12, 13 };
var query = from item in arr1 where item < 13 select item;
foreach (var item in query)
{
Console.Write($"{item} ");
}
join句
2つ以上のコレクションを結合するために使用されます。結合操作は、2つのコレクションを受け取り、各オブジェクトに両方の元のコレクションのすべてのフィールドを含む一時的なオブジェクトのコレクションを作成します。
join 型 結合変数 in コレクション2 on フィールド1 equals フィールド2
class Member
{
public int Id;
public string LastName;
public Member(int id, string lastName)
{
Id = id;
LastName = lastName;
}
}
class Enrollment
{
public string CourseName;
public int MemberId;
public Enrollment(string courseName, int memberId)
{
CourseName = courseName;
MemberId = memberId;
}
}
static Member[] members =
{
new Member(1, "Tanaka"),
new Member(2, "Sato"),
new Member(3, "Suzuki"),
};
static Enrollment[] enrollments =
{
new Enrollment("Math", 1),
new Enrollment("Math", 2),
new Enrollment("History", 1),
new Enrollment("History", 3),
new Enrollment("Physics", 3)
};
static void Main(string[] args)
{
var query = from m in members
join e in enrollments on m.Id equals e.MemberId
where e.CourseName == "History"
select m.LastName;
foreach (var result in query)
{
Console.WriteLine($"History course student: {result}");
}
}
let句
式の計算結果を、後続の操作で使用する識別子に代入します。
let 識別子 = 式
int[] groupA = { 3, 4, 5, 6 };
int[] groupB = { 6, 7, 8, 9 };
var query = from a in groupA
from b in groupB
let sum = a + b
where sum == 12
select new { a, b, sum };
foreach (var item in query)
{
Console.WriteLine($"{item}");
}
where句
指定された条件に基づいて項目をフィルタリングします。
where ブール式
int[] groupA = { 3, 4, 5, 6 };
int[] groupB = { 6, 7, 8, 9 };
var query = from a in groupA
from b in groupB
let sum = a + b
where sum >= 11
where a == 4
select new { a, b, sum };
foreach (var item in query)
{
Console.WriteLine($"{item}");
}
orderby句
式に基づいて結果を並べ替えます。デフォルトは昇順ですが、ascendingまたはdescendingキーワードで順序を指定できます。
orderby 式 ascending, 式 descending, ...
var members = new[]
{
new { LastName = "Jones", FirstName = "Mary", Age = 19, Major = "History" },
new { LastName = "Smith", FirstName = "Bob", Age = 20, Major = "ComputerScience" },
new { LastName = "Fleming", FirstName = "Carol", Age = 20, Major = "History" }
};
var query = from m in members
orderby m.Age ascending, m.LastName descending
select m;
foreach (var item in query)
{
Console.WriteLine($"{item}");
}
select...group句
クエリの結果として返す項目を指定します。
select:選択するオブジェクトの一部(フィールドや新しいオブジェクト)を指定します。group...by:選択された項目をグループ化するために使用します。
select 式
group 式1 by 式2
var members = new[]
{
new { LastName = "Jones", FirstName = "Mary", Age = 19, Major = "History" },
new { LastName = "Smith", FirstName = "Bob", Age = 20, Major = "ComputerScience" },
new { LastName = "Fleming", FirstName = "Carol", Age = 20, Major = "History" }
};
// 複数のフィールドからなる新しい匿名オブジェクトを作成
var query = from m in members
select new { m.LastName, m.FirstName, Token = "2023Member2023" };
foreach (var item in query)
{
Console.WriteLine($"{item.LastName}, {item.FirstName}, {item.Token}");
}
group by句
指定された基準に基づいて選択されたオブジェクトをグループ化します。
group 型 項目 by 項目.フィールド
var members = new[]
{
new { LastName = "Jones", FirstName = "Mary", Age = 19, Major = "History" },
new { LastName = "Smith", FirstName = "Bob", Age = 20, Major = "ComputerScience" },
new { LastName = "Fleming", FirstName = "Carol", Age = 20, Major = "History" }
};
var query = from m in members
group m by m.Major;
foreach (var group in query)
{
Console.WriteLine($"----- {group.Key} -----");
foreach (var member in group)
{
Console.WriteLine($"{member.LastName} · {member.FirstName}");
}
}
into句(クエリ継続)
クエリの一部の結果を名前を付けて、後続のクエリで使用できるようにします。
into 変数
join 型 結合変数 in 式 on 式 equals 式 into 変数
int[] groupA = { 3, 4, 5, 6 };
int[] groupB = { 6, 7, 8, 9 };
var query = from a in groupA
join b in groupB on a equals b
into joinedGroup
from c in joinedGroup
select c;
foreach (var item in query)
{
Console.WriteLine($"aとbの共通要素: {item}");
}
標準クエリ演算子
標準クエリ演算子は、LINQクエリを構成する一連のAPIメソッドです。これらの演算子は、IEnumerable<T>インターフェイスを実装する任意の.NET配列またはコレクションをクエリするために使用できます。
| 演算子 | 説明 |
|---|---|
Where |
指定された述語に基づいてシーケンスをフィルタリングします。 |
Select |
オブジェクトまたはオブジェクトの一部を指定して選択します。 |
SelectMany |
コレクションのコレクションを返し、それらの結果を1つのコレクションにマージします。 |
Take |
シーケンスの先頭から指定された数の要素を返します。 |
Skip |
シーケンスの先頭から指定された数の要素をスキップします。 |
TakeWhile |
述語がtrueを返す限り、現在の項目を選択し、falseになった時点で残りの項目を破棄します。 |
SkipWhile |
述語がtrueを返す限り、現在の項目をスキップし、falseになった時点で残りの項目を選択します。 |
Join |
2つのシーケンスに対して内部結合を実行します。 |
GroupJoin |
階層的な結果を生成する結合。最初のシーケンスの各要素が、2番目のシーケンスの要素に関連付けられます。 |
Concat |
2つのシーケンスを連結します。 |
OrderBy/ThenBy |
1つ以上のキーに基づいてシーケンスの要素を昇順で並べ替えます。 |
Reverse |
シーケンスの要素の順序を反転させます。 |
GroupBy |
シーケンスの要素をグループ化します。 |
Distinct |
シーケンスから重複する要素を削除します。 |
Union |
2つのシーケンスの和集合を返します。 |
Intersect |
2つのシーケンスの積集合を返します。 |
Except |
最初のシーケンスの要素から、2番目のシーケンスにも存在する重複要素を削除して返します。 |
ToArray |
シーケンスを配列として返します。 |
ToList |
シーケンスをList<T>として返します。 |
First/FirstOrDefault |
シーケンスの最初の要素、または述語に一致する最初の要素を返します。 |
Last/LastOrDefault |
シーケンスの最後の要素、または述語に一致する最後の要素を返します。 |
Single/SingleOrDefault |
シーケンスの唯一の要素、または述語に一致する唯一の要素を返します。 |
Count/Sum/Min/Max/Average |
シーケンスの要素数、合計、最小値、最大値、平均値を計算します。 |
LINQ to XML
LINQ to XMLは、メモリ内でXMLツリーを作成、操作、およびクエリするためのAPIを提供します。単一の言語を使用してXMLツリーをトップダウンで構築できます。
XMLツリーの作成と操作
関数型構築を使用して、1つのステートメントでXMLツリー全体を作成できます。
XDocument doc = new XDocument(
new XElement("Employees",
new XElement("Employee",
new XElement("Name", "Alice"),
new XElement("Department", "HR")
),
new XElement("Employee",
new XElement("Name", "Bob"),
new XElement("Department", "IT")
)
)
);
Console.WriteLine(doc);
XMLツリーのクエリ
XMLツリーをクエリするには、LINQクエリを使用します。
XDocument doc = XDocument.Load("employees.xml");
XElement root = doc.Element("Employees");
var query = from e in root.Elements("Employee")
where (string)e.Element("Department") == "IT"
select e.Element("Name").Value;
foreach (var name in query)
{
Console.WriteLine($"IT Department Employee: {name}");
}