|
|
連載:[完全版]究極の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.PointList
where 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.PointList
where n.point >= get80()
select n;
|
|
この場合、get80メソッドはローカル上で計算可能であり、その計算結果である「80」をサーバーへ送ることでクエリは成立する。