LINQにも色々 ~SQLに変換されるモノと変換されないモノ
Visual StudioでDB連携も簡単プログラミング-知っておきたいLINQメソッド式&ラムダ式 第4回
ダウンロード サンプルファイル (9.8 MB)
本連載では、データベースプログラミングにおいてLINQをどのように活用できるのか、解説していきます。前回の記事では、Entity Frameworkが出力するSQLを確認し、N+1問題と呼ばれる、遅延ローディングに伴って発生するパフォーマンス上の問題への対応方法を確認しました。連載最終回となる今回は、LINQがSQLに変換される仕組みについて、もう少し細かく追って行きましょう。
-
- Page 1
LINQにも色々
LINQからSQLへの変換について説明するにあたり、LINQのバリエーションについて少し解説しておきます。第1回で説明したとおり、LINQの対象は、配列・コレクション・データベース・XMLなど多岐にわたっています。これまでLINQとしてひとくくりに扱ってきましたが、クエリ対象によって幾らかのバリエーションがあります(表1)。
| LINQの種類 | クエリ対象 |
|---|---|
| LINQ to Objects | 配列・コレクションなど、任意のオブジェクト |
| LINQ to Entities | Entity Framework経由でデータベース(SQL Server/Oracle/MySQL/SQLite等) |
| LINQ to XML | XML |
| LINQ to SQL | SQL Server |
過去2回のデータベースプログラミングで解説してきたのは、Entity Framework経由でデータベースにアクセスするLINQ to Entitiesです。XMLを対象とするLINQ to XMLはやや毛色が異なるため本連載では扱いません。また、LINQ to SQLは、Entity Framework登場以前に使われていたSQL Server専用の機能のため、こちらも割愛します。
SQLに変換されないモノ
さて、LINQについて紹介する記事では、配列やコレクションなどをクエリ対象とするLINQ to Objectsを使って解説されることが多いのですが、実はLINQ to ObjectsとLINQ to Entitiesには違いがあり、LINQ to Objectsでは普通に動くLINQが、LINQ to Entitiesでは動かない、というケースがしばしば生じます。
たとえば、リスト1はLINQ to Objectsで、文字列長が6かどうかでフィルタするサンプルです。
//LINQ to Objects クエリ対象文字列配列
string[] names = { "Doi", "Tanaka", "Nakamura", "Saitou", "Yamada" };
//①文字列長6のものだけ取り出す
var query = names.Where(x => x.Length == 6);
//出力結果:Tanaka,Saitou,Yamada
Console.WriteLine(string.Join(",", query));
//②条件をIsLength6メソッドに置き換えてみる
query = names.Where(x => IsLength6(x));
//出力結果は変わらない
Console.WriteLine(string.Join(",", query));
//文字列の長さが6かどうかを判別するメソッド
private bool IsLength6(string str)
{
return str.Length == 6;
}
①では普通にラムダ式を使っているのに対し、②では自分で定義したIsLength6メソッドを使っています。「文字列の長さが6かどうか」という検索条件は変わりませんので、当然出力結果は同じです。
続いて同じようにLINQ to Entitiesで書いてみましょう(リスト2)。
//①シンプルに文字列長でフィルタ products = context.Products.Where(x => x.Name.Length == 6); DumpProducts(products); //問題無く出力される //②IsLength6メソッドを使う products = context.Products.Where(x => IsLength6(x.Name)); DumpProducts(products); //例外発生!!!
今度は不可解な結果となりました。①のWhereメソッドは「WHERE 6 = ( CAST(LEN([Extent1].[Name]) AS int))」のようなWHERE文の付いたSQLに正しく変換され、その後の処理にも問題はありません。一方自分で定義したIsLength6メソッドを使った②では、System.NotSupportedException例外が発生します。①と②は処理内容としてはそれほど変わったように見えないのですが、何が違うのでしょうか?
NotSupportedException例外のメッセージを見ると、「メソッド 'Boolean IsLength6(System.String)' は LINQ to Entities では認識されないため、ストア式に変換できません。」とあります。やはりIsLength6というメソッドがLINQ to Entitiesでは認識されず、使用することができないようです。
「LINQ to Objectsで動くのに、LINQ to Entitiesで動かないなんて……」と気を落とすことはありません。この違いは、図1のように、LINQ to ObjectsとLINQ to Entitiesでは、LINQが実際に実行される方法の違いによって発生します。
LINQ to Objectsでは、LINQがそのまま、C#のプログラムと同様に実行されます。そのため、LINQの中から自分が定義したメソッドなどを自由に呼び出すことができます。一方、LINQ to Entitiesでは、LINQはSQLに変換され、データベース上で実行されます。そのため、元々のC#プログラム内で定義したメソッドなどをLINQの中から呼び出すことはできません。つまり「LINQ to Entitiesで使用する機能はすべてSQLに変換可能でなければならない」のです。仕組みを考えてみると当たり前ではあるのですが、LINQに慣れて、色々なクエリを試すようになると、引っかかりやすいポイントでもあります。
この記事は参考になりましたか?
- Visual StudioでDB連携も簡単プログラミング ~知っておきたいLINQメソッド式&ラムダ式連載記事一覧
-
- LINQにも色々 ~SQLに変換されるモノと変換されないモノ
- N+1問題を回避せよ! LINQから出力されるSQLを見てみよう&遅延ローディングの光と闇
- DB開発でありがちな悩みを解決! Visual Studioと組合せて便利なLINQメソッ...
- この記事の著者
-
山田 祥寛(ヤマダ ヨシヒロ)
静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
WINGSプロジェクト 土井 毅(ドイ ツヨシ)
<WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア

