[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さんをフォロー
コメント