前回のおさらい
前回の記事では、データベースに対してLINQメソッド式を使ってクエリを実行する方法について解説しました。多数の条件が指定された場合でも、メソッドチェーン式にメソッドを積み重ねることで簡単に実装できることを確認できました。
今回は、LINQが実際にはどんなSQLに変換されているのかを確認します。また、遅延ローディングの有無によってどのように処理が変わるかを意識し、パフォーマンス問題を解決する方法について解説します。なお、前回作成したサンプルを今回も引き続き使用します。
出力されるSQLを確認してみよう
前回の解説通り、Entity Frameworkは、LINQのクエリ式やメソッド式を、最終的にSQLへと変換してデータベース上で実行しています。この変換は透過的に行われるため、プログラマはLINQを記述する際にSQLをそれほど意識する必要がありません。しかし、データベースプログラミングを行っている以上、実際にどんなSQLが実行されているかを理解するのは重要です。自分が書いたLINQがどのようなSQLとして実行されるかが理解できていないと、後ほど解説するようなパフォーマンス上の問題にぶつかることになります。データベースプログラミングにおいては「ただクエリ結果が正しければいい」だけでなく「データベースに不要な負荷を掛けないようにクエリを投げる」のも重要です。
Entity Frameworkから実際に出力されるSQLを確認するには、リスト1の①のように、コンテキストクラスのDatabaseプロパティで取得できるSystem.Data.Entity.DatabaseオブジェクトのLogプロパティに、ログを出力するためのメソッドを指定します。なお、この機能はEntity Framework 6から追加されたものです。これだけで、Entity FrameworkからSQLを実行する際、SQLの内容やパラメータ、実行時間などが指定したメソッドの引数として渡されます。
using (var context = new CodeZineSampleContext()) { //①コンテキストクラスのDatabase.Logプロパティを使って //実際に発行されるSQLをコンソールに出力する context.Database.Log = Console.WriteLine; //②全件出力してみる IQueryable<Product> products = context.Products; //③結果をループして出力する foreach (var product in products) { //商品名だけ出力 Console.WriteLine(product.Name); } }
出力結果はリスト2のようになります。
2014/10/26 0:33:35 +09:00 で接続を開きました SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Price] AS [Price], [Extent1].[Employee_Id] AS [Employee_Id], [Extent1].[Maker_Id] AS [Maker_Id] FROM [dbo].[Products] AS [Extent1] -- 2014/10/26 0:33:35 +09:00 で実行しています -- 6 ミリ秒で完了しました。結果: SqlDataReader
実際には、上記のSQLProductsテーブルへアクセスする前に、[INFORMATION_SCHEMA.TABLES]や[__MigrationHistory]テーブルにアクセスするSQLが発行されます。これはEntity Frameworkがソースコードのエンティティとデータベースのテーブル定義の同期を取るために、データベース初回アクセス時に自動的に発行されるもので、LINQとは無関係です。
SQLの内容としては、ProductsテーブルのすべてのフィールドをSELECTするだけのシンプルなものです。特に条件を指定しない全件出力ですので、想定通りのSQLと言っていいでしょう。
さて、ログが出力されたタイミングにも注目しましょう。ブレークポイントを②の箇所に仕掛けてデバッグ実行し、一行ずつステップ実行してみてください。②の全件出力の時点ではSQLは発行されず、③のforeach文を実行するタイミングでログが出力されることが確認できるでしょう。前回も解説しましたが、Entity Frameworkは、実際にデータを使うタイミングになって初めてSQLの発行を行います。
Whereメソッドで条件を指定した場合のSQL
続いて、LINQのWhereメソッドを使って条件を指定した場合のSQLも確認してみましょう(リスト3)。
//Priceが300を超えるものだけ出力する IQueryable<Product> products = context.Products.Where(x => x.Price > 300); ↓ SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Price] AS [Price], [Extent1].[Employee_Id] AS [Employee_Id], [Extent1].[Maker_Id] AS [Maker_Id] FROM [dbo].[Products] AS [Extent1] WHERE [Extent1].[Price] > 300
「x => x.Price > 300」というラムダ式は「WHERE [Extent1].[Price] > 300」というSQLに変換されています。こちらも概ね想定通りですね。