[Entity Framework] LINQ で遅延評価のプロパティを先読みしてパフォーマンスを上げる
こちらのエントリー で、JOIN を使わないで複数のテーブルからデータを取得する方法をご紹介しました。
しかし、この方法だと、遅延評価にしているプロパティ(子テーブル)にアクセスする際に、ループの回数分データベースにアクセスに行ってしまうのでパフォーマンスが悪化してしまいます。
この問題を回避する方法が分かりましたので、ご紹介します。
遅延評価プロパティのパフォーマンスを改善する方法
その方法とは、LINQ でデータを読み込む際に、Include メソッドを指定することで、引数に指定されたプロパティを先読みするというものです。
以下のプログラムが、LINQ で遅延評価を先読みするように変更したものです(赤字部分)。なお、このプログラムは SQL ログをデバッグ出力しています。
・Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using System.Diagnostics;namespace EFRelation
{
class Program
{
static void Main(string[] args)
{
//データベース初期化
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<BlogContext>());using (var context = new BlogContext())
{
//SQLログ設定
context.Database.Log = Logger;var post1 = new Post(){
Author="山田太郎",
Title="いい天気",
Body="こんにちは。いい天気です。",
Posted = DateTime.Now};context.Posts.Add(post1);
var comment1 = new Comment() {
Author = "山田花子",
Body ="こんにちは",
Posted = DateTime.Now,
Post = post1};context.Comments.Add(comment1);
var comment2 = new Comment() {
Author = "佐藤次郎",
Body = "よろしくお願いします。",
Posted = DateTime.Now,
Post = post1};context.Comments.Add(comment2);
context.SaveChanges();
//遅延評価の Comments プロパティを先読み
var posts = from t in context.Posts.Include("Comments")
orderby t.PostId
select t;foreach (var post in posts)
{
Console.WriteLine("[Post]");
Console.WriteLine(post.PostId.ToString());
Console.WriteLine(post.Author);
Console.WriteLine(post.Title);
Console.WriteLine(post.Body);
Console.WriteLine(post.Posted.ToLongDateString());foreach (var comment in post.Comments)
{
Console.WriteLine("[Comment]");
Console.WriteLine(comment.CommentId.ToString());
Console.WriteLine(comment.Author);
Console.WriteLine(comment.Body);
Console.WriteLine(comment.Posted.ToLongDateString());
}
}
Console.ReadKey();
}
}private static void Logger(string message)
{
Debug.Write(message);
}}
}
以下のものが、遅延評価のプロパティを先読みした場合の SQL ログになります。LEFT JOIN で Comment を先読みしていることが分かります。
SELECT
[Project1].[PostId] AS [PostId],
[Project1].[Author] AS [Author],
[Project1].[Title] AS [Title],
[Project1].[Body] AS [Body],
[Project1].[Posted] AS [Posted],
[Project1].[C1] AS [C1],
[Project1].[CommentId] AS [CommentId],
[Project1].[Author1] AS [Author1],
[Project1].[Body1] AS [Body1],
[Project1].[Posted1] AS [Posted1],
[Project1].[PostId1] AS [PostId1]
FROM ( SELECT
[Extent1].[PostId] AS [PostId],
[Extent1].[Author] AS [Author],
[Extent1].[Title] AS [Title],
[Extent1].[Body] AS [Body],
[Extent1].[Posted] AS [Posted],
[Extent2].[CommentId] AS [CommentId],
[Extent2].[Author] AS [Author1],
[Extent2].[Body] AS [Body1],
[Extent2].[Posted] AS [Posted1],
[Extent2].[PostId] AS [PostId1],
CASE WHEN ([Extent2].[CommentId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [dbo].[Posts] AS [Extent1]
LEFT OUTER JOIN [dbo].[Comments] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[PostId]
) AS [Project1]
ORDER BY [Project1].[PostId] ASC, [Project1].[C1] ASC
-- Executing at 2014/01/04 17:08:50 +09:00
-- Completed in 1 ms with result: SqlDataReader
なお、遅延評価の先読みを行わない場合の SQL ログは以下のようになります。LEFT JOIN の指定がなくなっていますね。
SELECT
[Extent1].[PostId] AS [PostId],
[Extent1].[Author] AS [Author],
[Extent1].[Title] AS [Title],
[Extent1].[Body] AS [Body],
[Extent1].[Posted] AS [Posted]
FROM [dbo].[Posts] AS [Extent1]
ORDER BY [Extent1].[PostId] ASC
-- Executing at 2014/01/04 17:29:02 +09:00
-- Completed in 0 ms with result: SqlDataReader
まとめ
Entity Framework で、遅延評価プロパティを先読みすることでパフォーマンスを改善する方法を見てきました。
この情報はあまり知られてなさそうなので、広まるといいですね。
参考サイト
ちょっと気になるのが以下の部分。遅延読み込みがサポートされ、記述が簡潔になっていますが、一括で読み込んだ方がパフォーマンスはよいのではないかと思います。違うのでしょうか?
Entity Framework 4 では遅延読み込みがサポートされているので、複雑に考えることなく関連オブジェクトの利用が出来るようになっています。
スポンサーリンク
Twitter ではブログにはない、いろんな情報を発信しています。
@fnyaさんをフォロー
コメント