|  |  | 
 
| 連載:[完全版]究極のC#プログラミングChapter17 LINQ to SQL川俣 晶2010/04/14
 |  | 
| 
 | 
17.4 LINQ to SQLのサンプル
 本書は、あくまでC# 3.0入門である。LINQ to SQLは、C# 3.0の外部に存在する独立した技術であり、Visual Basicなどでも利用されるものである。そのため、本書ではC# 3.0プログラマーとLINQ to SQLの関わりを示し、簡単なサンプルを提示する程度にとどめる。前者についてはすでに述べたので、あとは簡単なサンプルを1つだけ提示して終わろう。とはいえ、C#プログラマーとして見るべきところは多い。
 ここでは、図17.3、図17.4のようなテーブルPointListが存在するという前提で始めよう。
|  
 | 
| 図17.3 PointListテーブルの設計 | 
|  | 
 以降では、このテーブルに含まれる、pointが80以上の人を検索してみよう。
 まず、Visual Studio 2008でコンソールアプリケーションのプロジェクトを作成する。次に、このプロジェクトに「LINQ to SQLクラス」を追加する。これはテンプレートから選ぶだけなので簡単だろう。
|  | 
| 図17.4 PointListテーブルの内容 | 
 追加すると即座にO/Rデザイナが開かれて、図17.5のような表示になる。
|  | 
| 図17.5 LINQ to SQLクラス追加時に開くO/Rデザイナ | 
 これを使って、O/Rマッピングを作成してみよう。やり方は簡単で、サーバーエクスプローラ上の[PointList]という項目を中央の領域へドラッグ&ドロップするだけである(図17.6参照)。
 これでコードを書く準備はできた。Program.csにリスト17.1のようなコードを書いてみよう。
|  | 
| 図17.6 生成されたO/Rマッピング | 
| 
 
| using System;using System.Linq;
 
 namespace ConsoleApplication78
 {
 class Program
 {
 static void Main(string[] args)
 {
 var db = new DataClasses1DataContext();
 
 var query = from n in db.PointList
 where n.point >= 80
 select n;
 
 foreach (var m in query)
 {
 Console.WriteLine("{0} {1} {2}points",
 m.name,m.date.ToString("yyyy/MM/dd"),
 m.point);
 }
 // 出力
 // 花子     2009/02/28 90points
 // 三郎     2008/12/31 100points
 }
 }
 }
 
 |  | 
| リスト17.1 LINQ to SQLによるデータベースの検索 | 
 ここで、DataClasses1DataContextクラスはO/RマッピングとしてO/Rデザイナが自動生成してくれたクラス名である。これを使えば、db.PointListと書くだけでPointListテーブルに対するマッピングを参照でき、それはクエリ式でクエリできる。
 ちなみに、ここでは「m.date.ToString("yyyy/MM/dd")」の部分に注目しよう。この書式指定付きToStringメソッドはDateTime構造体に固有のものである。このようなコードが記述可能であるのは、PointListテーブルのdate列がDateTime型のプロパティにマッピングされているからである。つまり、SQLの型とC#の型がマッピングによって連携しており、型に関するトラブルが飛躍的に起こりにくくなっている。
 さて、ここで注意すべき点がある。このクエリ式は、実際にはC#プログラムとしては実行されていない点である。つまり、「n.point >= 80」という式は、C#プログラムとして実行されることはない。
 常識的なC#プログラマーの感覚でいえば「そんなばかな」と思うかもしれないが、それは事実である。では、どのようにしてそれを証明できるだろうか?
 その方法は簡単で、変数dbをnewしている行の次に、次に示した行を挿入するだけである。
 これで、どのようなSQL文がSQL Serverに送信されているかが手に取るようにわかる。
| 
 
| SELECT [t0].[id], [t0].[name], [t0].[date], [t0].[point]FROM [dbo].[PointList] AS [t0]
 WHERE [t0].[point] >= @p0
 -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [80]
 -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1
 
 花子     2009/02/28 90points
 三郎     2008/12/31 100points
 
 |  | 
| リスト17.1でSQL Serverに送信されているSQL文 | 
 この3行目は、pointが「@p0」以上である場合という条件を示し、4行目で、@p0は80であることが示されている。つまり、このクエリの中には「n.point >= 80」という条件そのものが埋め込まれ、SQL Server側で処理されているのである。
 このことは、クエリがサーバー側で処理され、結果だけが返ってくることを示す。大量のデータをまず取得して検索を行うようなネットワークの無駄は排除されている。
 一方で、このような仕掛けが成立する理由は、本章の冒頭でも説明したとおり、「式本体を持つラムダ式は、式ツリーに変換できる」というC# 3.0の機能にある。LINQのクエリ式を式ツリーに変換したうえで、それをさらにSQL文に翻訳しているわけである。
 このことは、式ツリーに翻訳できてもSQL文に翻訳できない式は扱えないことを示す。たとえば、条件判定がローカル上のメソッドに依存するような式はSQL文に翻訳できない。SQL Server側に同等のメソッドが存在しないからである。
 たとえば、Programクラスに次のメソッドを追加してみよう。
| 
 
| private static bool check(int n){
 return n >= 80;
 }
 
 |  | 
 そして、クエリを次のように書き直してみよう。
| 
 
| var query = from n in db.PointListwhere check(n.point)
 select n;
 
 |  | 
 このプログラムは構文的には正しいのでコンパイル可能である。しかし、実行すると次のような例外を出して止まる。
| 
 
| System.NotSupportedException はハンドルされませんでした。メソッド 'Boolean check(Int32)' には、サポートされる SQL への変換がありません。
 
 |  | 
 SQL Server側からはcheckメソッドは直接呼び出せないし、仮にネットワークを越えて呼び出したとしても、時間がかかりすぎてクエリ速度が非現実的に遅くなるだけだろう。
 ただし、次のようなメソッドを用意した場合はクエリできる。
| 
 
| private static int get80(){
 return 80;
 }
 
 |  | 
 クエリは次のように記述する。
| 
 
| var query = from n in db.PointListwhere n.point >= get80()
 select n;
 
 |  | 
 この場合、get80メソッドはローカル上で計算可能であり、その計算結果である「80」をサーバーへ送ることでクエリは成立する。