特集:VBプログラマーのためのLINQ超入門(後編)LINQによるデータベース・アクセスとO/Rマッピングデジタルアドバンテージ 遠藤 孝信2009/05/08 |
|
本稿は、すでに公開されている「特集:C#プログラマーのためのLINQ超入門(後編)LINQによるデータベース・アクセスとO/Rマッピング」を、Visual Basicプログラマー向けに加筆・修正したものです。 |
本特集の前編では、次のような簡単なLINQ(リンク)のクエリについて解説しました。
| |
リスト1 LINQのクエリ |
前編で述べているように、クエリ内のWhere句やSelect句は、コンパイル時にメソッドの呼び出しに変換されます。実際、LINQのクエリ構文をまったく使用せずに、メソッド呼び出しだけで同等の処理を記述できます。このような記述方法は、クエリ構文(query syntax)に対して、メソッド構文(method syntax)と呼ばれます。
後編となる今回は、リスト1をメソッド構文で書き換えるところから始めます。
LINQのメソッド構文
とはいっても、リスト1の場合には書き換えは簡単で、これは次のようになります。
| |
リスト2 リスト1のクエリをメソッド構文で記述 |
突然現れた「Function」はVisual Basic 2008(=Visual Basic 9.0。以降、VB 9.0)の新機能である「ラムダ式」を記述するためのもので、これは「Function式」とも呼ばれます(メソッド定義のためのFunctionとは別物です)。これについては後述します。
リスト2は改行して3行で記述しているので少し分かりにくいですが、このコードの大枠は次のような1行です。
| |
リスト3 リスト2を1行で記述(引数は省略) |
つまり、変数dataに対してWhereメソッドを呼び出し、さらにその結果に対してSelectメソッドを呼び出しているだけです。
■ラムダ式を匿名メソッドに
WhereとSelectの2つのメソッドで引数に指定している「Function(…) ……」の部分は、「ラムダ式」と呼ばれるものです。
ラムダ式とは、簡単にいうと、名前のない関数をシンプルに定義するための記述方法です。例えば、「n = n + 1」という関数自体を、あるメソッドに引数として渡したい場合、これまでは中身が1行だけのメソッドを定義し、そのデリゲートを渡す必要がありました。VB 9.0では、そのようなメソッドを「Function(n) n + 1」というラムダ式で記述でき、これをそのままメソッドに渡すことができます。
ラムダ式は、機械的にデリゲートに置き換えることができます。リスト2の場合は次のように書き換えることができます。ここでは実際にコンパイルして実行できるように、クエリ以外のコードも示しています。
| |
リスト4 リスト2のラムダ式部分をデリゲートで記述 | |
このコードを実行するには、Visual Studio 2008でコンソール・アプリケーションのプロジェクトを新規作成し、Module1.vbの内容と置き換える。 |
ここではデリゲート型として、汎用的なFunc(Of T1, T2)デリゲート型を使用しています。このデリゲート型については、「.NET TIPS:汎用的に使用できる定義済みのデリゲート型は?」を参照してください。
ここで少しまとめておきましょう。Whereメソッドはフィルタの役目を果たします。つまり、「data.Where(……)」は配列dataの各要素に対して、指定されたフィルタ条件に合致する要素のみを返します。
フィルタ条件はどうやって指定するかというと、True/Falseを返すメソッドへのデリゲートをWhereメソッドの引数として渡します。Whereメソッド内部では、判定が必要な個所でそのメソッドが使われます。
一方、Selectメソッドはクエリの結果の要素を作成するためのものです。クエリ結果の要素となるオブジェクトを作成するメソッドへのデリゲートを引数にして呼び出します。リスト1〜4では、Ordersオブジェクト(変数n)をそのまま返していますが、もちろん何を返すかはプログラマーの自由です。
リスト4のメソッド構文による記述を、元のクエリ構文(コメントで記述している部分)と見比べてみてください。結局、Where句やSelect句などに記述できる式は、デリゲートとなるメソッドがReturn文で返す式、ということになります。
■匿名型のオブジェクトを返すSelect句
本題とは少し離れますが、ここでSelect句が返す要素について解説しておきます。
リスト4までは、Select句はOrdersオブジェクトを返していますが、クエリ結果として、例えばOrderIDとEmployeeIDの値だけを返したいという場合には、どうすればよいでしょうか。
1つのストレートな方法としては、まず次のようなOrderIDとEmployeeIDだけを持つOrderEmployeeクラスとそのコンストラクタを定義しておき……、
| |
リスト5 OrderEmployeeクラスの定義 |
クエリのSelect句の部分を次のように書き換えるという手があります。
| |
リスト6 Select句でOrderEmployeeオブジェクトを返すクエリ |
もちろんこれでも目的は達成できますが、いちいちクラスを定義するのは非常に面倒です。そこで、VB 9.0では自動的にクラス(およびコンストラクタ)を定義してくれる「匿名型」という機能が導入されています。
匿名型を使えば、次のようなクエリを記述するだけでコトが足ります。
| |
リスト7 匿名型のオブジェクトを返すSelect句 |
最後の行の「New With { …… }」の記述が、匿名型を使用している個所です。これだけで、OrderIDとEmployeeIDというプロパティを持つクラスが自動的に定義され、それぞれのプロパティにn.OrderIDとn.EmpolyeeIDの値が代入されたオブジェクトが作成されます。
ただし、自動的に定義されたクラスのクラス名を知ることはできません。そのため、そのオブジェクトを変数で受け取るには、代入されるオブジェクトの型を自動的に設定してくれる、As句なしの「Dim」が不可欠となります(リスト7の1行目)。
匿名型は、クエリの結果の作成に必要な、一時的なオブジェクトを作成するために導入された機能であり、As句なしのDimステートメントはそのオブジェクトを受け取るために導入された機能であるというわけです*1。
*1 型推論のためのAs句なしDimによるローカル変数の宣言は、ほかの場面でも便利に使えますが、匿名型はLINQのSelect句以外には使い道はなさそうです。 |
クラス名も分からずにそのオブジェクトが扱えるのか? と少し心配になりますが、リスト7のクエリの結果へのアクセスは、As句なしのFor Eachステートメントを使うことにより、次のようにして行えます。
| |
リスト8 リスト7のクエリ結果の表示 |
For Eachステートメントで使用されている変数rは、これ以前にその型が宣言されているわけではなく、コレクションである変数recordsから推論可能であるため、As句を省略できます。
ちなみに、リスト7の匿名型オブジェクト作成の部分は、次のようにプロパティ名を省略できます。
|
■拡張メソッドであるWhereメソッドとSelectメソッド
ところで、リスト4では変数dataは単なる配列です。その配列に対してWhereメソッドを呼び出しているわけですが、配列(つまりはSystem名前空間のArrayクラス)にWhereメソッドなんかあったでしょうか?
実は、Whereメソッドは、「拡張メソッド」と呼ばれるVB 9.0の新機能により実現されています。拡張メソッドは、既存のクラスに対して、クラスの外部からメソッドを追加できる仕組みです。この場合では、配列(Arrayクラス)にWhereメソッドが追加される形となっています。
実際には、配列が実装しているIEnumerable(Of T)インターフェイス(これはジェネリック・インターフェイスです)に対して、Whereメソッドが追加されています。これにより、IEnumerable(Of T)インターフェイスを実装しているすべてのオブジェクトに対して、Whereメソッドを呼び出すことができます。Selectメソッドも同様です。
なお、前編でも解説したように、WhereメソッドやSelectメソッドの本体自体はEnumerableクラス(System.Linq名前空間)で定義されています。Enumerableクラスには、グループ化のためのGroupByメソッドや、2つのデータソースを結合するためのJoinメソッドなど、LINQ用の標準クエリ演算子が拡張メソッドとして多数定義されています。
■クエリ構文とメソッド構文、どちらを使う?
以上のように、LINQでクエリは、クエリ構文(リスト1のパターン)でもメソッド構文(リスト2のパターン)でも記述することができます。気に入った方で記述すればよいでしょう。
ただし、Where句やSelect句などのようなキーワードがEnumerableクラスのメソッドすべてに対して用意されているわけではないので、両者を混在させるしかない場合もあります。
例えば、検索結果の件数をカウントするには、EnumerableクラスのCountメソッドを使って次のように記述します。
| |
リスト9 検索結果の件数カウント(クエリ構文+メソッド呼び出し) |
こんなふうになるなら、最初から次のリスト10のように、すべてメソッド呼び出しで記述した方がよいという人も多いかもしれません。
| |
リスト10 検索結果の件数カウント(メソッド構文) |
しかし例えば、2つのデータソースを結合して検索を行う場合には、Joinメソッドの呼び出しよりも、Join句を使ったクエリ構文の方がすっきり記述できます。
次のリスト11は、Join句を使用したサンプル・プログラムです。ここでは先ほどのリスト4に、従業員(=employee)のIDと名前からなるEmployeesクラスのインスタンスの配列を追加し、クエリの結果において、従業員IDの代わりに従業員の氏名が表示されるようにしています(いわゆる内部結合です)。
| |
リスト11 Join句を使ったサンプル・プログラム |
長くなるため詳しくは説明しませんが、2つの配列に共通するEmployeeIDフィールドを軸に、Orders配列とEmployees配列を結合し、それに対してクエリを行っています。実行結果は次のようになります。
| |
リスト11の実行結果 |
しかし、リスト11のクエリを、メソッド構文を使って記述すると次のようになってしまいます。
| |
リスト12 Join句の代わりにJoinメソッドで記述したクエリ |
これを見るとJoin句の有り難みが分かります。
■
以上、LINQのクエリが処理されるカラクリについて少し触れましたが、実はここまでの話は、クエリのデータソースが配列やコレクションの場合、つまりLINQ to Objectでの話です。
クエリ自体の構文は同じですが、データソースがデータベースとなるLINQ to SQLではだいぶ中身が変わってきます。続いてはそんなLINQ to SQLについて説明します。
INDEX | ||
VBプログラマーのためのLINQ超入門(前編) | ||
LINQ(リンク)の基礎知識 | ||
1.ADO.NET+SQL文によるデータベースへの問い合わせ | ||
2.LINQによるデータベース/コレクションへの問い合わせ | ||
3.クエリの実行を支えるさまざまなLINQプロバイダ/LINQの書き方 | ||
4.そのほかのポイント/LINQPadの紹介 | ||
VBプログラマーのためのLINQ超入門(後編) | ||
LINQによるデータベース・アクセスとO/Rマッピング | ||
1.LINQのメソッド構文 | ||
2.データベースの問い合わせを行うLINQ to SQL | ||
3.O/RマッピングとLINQ to SQL | ||
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|