2014/01/04

[Entity Framework] LINQ で JOIN 句を使用して複数テーブルからデータを取得する


前回のエントリー では、JOIN 句を使用しないで複数テーブルからデータを取得する方法をご紹介しました。

しかし、この方法はデータを 1 回で取得せず、1:N の N のテーブルは必要になったときに取得する(遅延評価)ことと、全てのフィールドのデータを取得するのでパフォーマンスが若干よろしくないという部分があります。

今回は、JOIN 句を使用して複数テーブルから 1 回でデータを取得し、必要なフィールドのみ取得するようにします。

前提条件

以下の前提条件で動作検証しています。

  • Visual Studio Express 2013 for Windows Desktop
  • Entity Framework 6.0.2
  • SQL Server 2012 Express
  • コードファーストのコンソールアプリケーション

Entity Framework 関連

作成するデータベースはブログを想定しています。ブログのポスト(投稿)とコメントを管理します。ポストは Post クラス、コメントは Comment クラスになります。

・Post.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFRelation
{
  public class Post
  {
    public int PostId { get; set; }

    public string Author { get; set; }

    public string Title { get; set; }

    public string Body { get; set; }

    public DateTime Posted { get; set; }

    //コメント一覧 
    public virtual ICollection<Comment> Comments { get; set; }
  }
}

・Comment.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFRelation
{
  public class Comment
  {
    public int CommentId { get; set; }

    public string Author { get; set; }

    public string Body { get; set; }

    public DateTime Posted { get; set; }

    //外部キー
    //Post を設定すると自動的に更新される
    public int PostId { get; set; }

    //親フィールド、テーブルには作成されないが
    //このフィールドの更新が必要
    public virtual Post Post { get; set; }
  }
}

テーブルの定義を行うコンテキストクラスを作成する必要があります。

・BlogContext.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace EFRelation
{
  class BlogContext : DbContext
  {
    public DbSet<Post> Posts { get; set; }

    public DbSet<Comment> Comments { get; set; }
  }
}

App.config にデータベースの接続文字列を追加します。コンテキストクラスと同じ名前である必要があるので注意してください。

・App.config

<connectionStrings>
  <add name="BlogContext"
       connectionString="Data Source=(local)\SQLEXPRESS;Initial Catalog=Blog;Integrated Security=True;"
       providerName="System.Data.SqlClient" />
</connectionStrings>

データ更新・参照プログラム

データを更新・参照するプログラムは以下のようになります。

コメント追加時に、Post クラスのインスタンスを設定しているところがポイントです。

また、LINQ クエリ式で JOIN 句 を使用していますが、取得した結果を必要なフィールドにだけ絞っていること、AS 句のように別名を付けていることもポイントです。

なお、実際にどのような SQL が発行されたか分かるように、SQL ログをデバッグ出力しています。

・Progarm.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();

        var posts = from p in context.Posts
              join c in context.Comments
              on p.PostId equals c.PostId
              orderby p.PostId
              select new
              {
                p.PostId,
                PostAuthor = p.Author, //AS 句に該当 
                PostTitle = p.Title,
                PostBody = p.Body,
                CommentAuthor = c.Author,
                CommentBody = c.Body
              };

        int id = 0;

        foreach (var post in posts)
        {
          if (id != post.PostId)
          {
            Console.WriteLine("[Post]");
            Console.WriteLine(post.PostAuthor);
            Console.WriteLine(post.PostTitle);
            Console.WriteLine(post.PostBody);
            id = post.PostId;
          }

          Console.WriteLine("[Comment]");
          Console.WriteLine(post.CommentAuthor);
          Console.WriteLine(post.CommentBody);

        }

        Console.ReadKey();
      }
    }

    private static void Logger(string message)
    {
      Debug.Write(message);
    }

  }
}

実行してコンソールに出力される内容は以下のようになります。

[Post]
山田太郎
いい天気
こんにちは。いい天気です。
[Comment]
山田花子
こんにちは
[Comment]
佐藤次郎
よろしくお願いします。

また、SQL ログの JOIN を行った部分は以下のようになります。JOIN 句も正しく作成されていますし、必要なフィールドのみ抽出されていますね。AS 句による別名も正しく指定されています。これで、パフォーマンス上の問題は解決ですね。

SELECT
    [Extent1].[PostId] AS [PostId],
    [Extent1].[Author] AS [Author],
    [Extent1].[Title] AS [Title],
    [Extent1].[Body] AS [Body],
    [Extent2].[Author] AS [Author1],
    [Extent2].[Body] AS [Body1]
    FROM  [dbo].[Posts] AS [Extent1]
    INNER JOIN [dbo].[Comments] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[PostId]
    ORDER BY [Extent1].[PostId] ASC
-- Executing at 2014/01/04 8:38:57 +09:00
-- Completed in 3 ms with result: SqlDataReader

まとめ

Entity Framework で JOIN 句を使用して複数テーブルからデータを取得できることを見てきました。また、1回のデータ取得で必要なフィールドのみ取得しているので、パフォーマンスの問題も解決していることが分かりました。

前回の JOIN 句を使用しない方法は、分かりやすいのですが、パフォーマンスに難があるので、パフォーマンスが求められる場合はこちらの方法を採用することが望ましいですね。

関連エントリー

参考サイト


スポンサーリンク


このエントリーをはてなブックマークに追加




Twitter ではブログにはない、いろんな情報を発信しています。


コメント

コメントを書く



プロフィール

  • 名前:fnya
    経歴:
    SE としての経験は15年以上。様々な言語と環境で業務系システム開発を行い、セキュリティ対策などもしていました。現在は趣味SE。

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


    ブログについて

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

    現在、Enty で支援を受け付けています。もしよければご支援ください。



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

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