Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

LINQにも色々 ~SQLに変換されるモノと変換されないモノ

Visual StudioでDB連携も簡単プログラミング-知っておきたいLINQメソッド式&ラムダ式 第4回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2015/03/06 14:00

 本連載では、データベースプログラミングにおいてLINQをどのように活用できるのか、解説していきます。前回の記事では、Entity Frameworkが出力するSQLを確認し、N+1問題と呼ばれる、遅延ローディングに伴って発生するパフォーマンス上の問題への対応方法を確認しました。連載最終回となる今回は、LINQがSQLに変換される仕組みについて、もう少し細かく追って行きましょう。

目次

LINQにも色々

 LINQからSQLへの変換について説明するにあたり、LINQのバリエーションについて少し解説しておきます。第1回で説明したとおり、LINQの対象は、配列・コレクション・データベース・XMLなど多岐にわたっています。これまでLINQとしてひとくくりに扱ってきましたが、クエリ対象によって幾らかのバリエーションがあります(表1)。

表1 LINQの様々なバリエーション
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かどうかでフィルタするサンプルです。

リスト1 LINQ to Objectsで自分で定義したメソッドを呼んでみる
//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)。

リスト2 LINQ to Objectsで自分で定義したメソッドを呼んでみる
//①シンプルに文字列長でフィルタ
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が実際に実行される方法の違いによって発生します。

図1 LINQ to ObjectsとLINQ to Entitiesの実行方法の違い
図1 LINQ to ObjectsとLINQ to Entitiesの実行方法の違い

 LINQ to Objectsでは、LINQがそのまま、C#のプログラムと同様に実行されます。そのため、LINQの中から自分が定義したメソッドなどを自由に呼び出すことができます。一方、LINQ to Entitiesでは、LINQはSQLに変換され、データベース上で実行されます。そのため、元々のC#プログラム内で定義したメソッドなどをLINQの中から呼び出すことはできません。つまり「LINQ to Entitiesで使用する機能はすべてSQLに変換可能でなければならない」のです。仕組みを考えてみると当たり前ではあるのですが、LINQに慣れて、色々なクエリを試すようになると、引っかかりやすいポイントでもあります。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • WINGSプロジェクト 土井 毅(ドイ ツヨシ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2017年5月時点での登録メンバは52名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂き...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XMLD...

バックナンバー

連載:Visual StudioでDB連携も簡単プログラミング ~知っておきたいLINQメソッド式&ラムダ式
All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5