2014/01/04

[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
    経歴:
    SE としての経験は15年以上。様々な言語と環境で業務系システム開発を行い、セキュリティ対策などもしていました。現在は趣味SE。

    Twitter では、ブログでは取り上げない情報も公開しています。


    ブログについて

    このブログは、IT、スマートフォン、タブレット、システム開発などに関するさまざまな話題を取り上げたり、雑感などをつづっています。

    >>ブログ詳細
    >>自作ツール
    >>運営サイト
    >>Windows 10 まとめ

    Twitter のフォローはこちらから Facebook ページはこちら Google+ページはこちら RSSフィードのご登録はこちらから