関連エンティティの読み込み

Entity Framework では、関連データを読み込む方法として、一括読み込み、遅延読み込み、および明示的読み込みの 3 つの方法がサポートされています。 このトピックで紹介するテクニックは、Code First および EF Designer で作成されたモデルに等しく使用できます。

一括読み込み

一括読み込みとは、ある型のエンティティに対するクエリによって、関連するエンティティもクエリの一部として読み込まれるプロセスです。 一括読み込みは、Include メソッドを使用して実現されます。 たとえば、次のクエリでは、ブログと、各ブログに関連するすべての投稿が読み込まれます。

using (var context = new BloggingContext())
{
    // Load all blogs and related posts.
    var blogs1 = context.Blogs
                        .Include(b => b.Posts)
                        .ToList();

    // Load one blog and its related posts.
    var blog1 = context.Blogs
                       .Where(b => b.Name == "ADO.NET Blog")
                       .Include(b => b.Posts)
                       .FirstOrDefault();

    // Load all blogs and related posts
    // using a string to specify the relationship.
    var blogs2 = context.Blogs
                        .Include("Posts")
                        .ToList();

    // Load one blog and its related posts
    // using a string to specify the relationship.
    var blog2 = context.Blogs
                       .Where(b => b.Name == "ADO.NET Blog")
                       .Include("Posts")
                       .FirstOrDefault();
}

Note

Include は System.Data.Entity 名前空間の拡張メソッドであるため、必ずその名前空間を使用してください。

複数のレベルを一括で読み込む

関連エンティティの複数のレベルを一括で読み込むこともできます。 以下のクエリは、コレクション ナビゲーション プロパティと参照ナビゲーション プロパティの両方に対してこれを行う方法の例を示しています。

using (var context = new BloggingContext())
{
    // Load all blogs, all related posts, and all related comments.
    var blogs1 = context.Blogs
                        .Include(b => b.Posts.Select(p => p.Comments))
                        .ToList();

    // Load all users, their related profiles, and related avatar.
    var users1 = context.Users
                        .Include(u => u.Profile.Avatar)
                        .ToList();

    // Load all blogs, all related posts, and all related comments  
    // using a string to specify the relationships.
    var blogs2 = context.Blogs
                        .Include("Posts.Comments")
                        .ToList();

    // Load all users, their related profiles, and related avatar  
    // using a string to specify the relationships.
    var users2 = context.Users
                        .Include("Profile.Avatar")
                        .ToList();
}

Note

現時点では、読み込まれる関連エンティティをフィルター処理することはできません。 Include では、常にすべての関連エンティティが取り込まれます。

遅延読み込み

遅延読み込みとは、エンティティを参照するプロパティが初めてアクセスされたときに、エンティティまたはエンティティのコレクションがデータベースから自動的に読み込まれるプロセスです。 POCO エンティティ型を使用している場合、遅延読み込みは、派生プロキシ型のインスタンスを作成し、仮想プロパティをオーバーライドして読み込みフックを追加することによって実現されます。 たとえば、次のように定義されている Blog エンティティ クラスを使用している場合、Posts ナビゲーション プロパティが初めてアクセスされると、関連する Posts が読み込まれます。

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public string Tags { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

シリアル化のために遅延読み込みをオフにする

遅延読み込みとシリアル化は、うまく組み合わせることができません。注意しないと、遅延読み込みが有効になっているために、データベース全体に対してクエリを実行することになってしまいます。 ほとんどのシリアライザーは、型のインスタンスの各プロパティにアクセスすることで機能します。 プロパティ アクセスによって遅延読み込みがトリガーされ、より多くのエンティティがシリアル化されます。 これらのエンティティ プロパティがアクセスされると、さらに多くのエンティティが読み込まれます。 エンティティをシリアル化する前に、遅延読み込みをオフにすることをお勧めします。 以降のセクションでは、その方法について説明します。

特定のナビゲーション プロパティの遅延読み込みをオフにする

Posts プロパティを非仮想にすることで、Posts コレクションの遅延読み込みをオフにできます。

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public string Tags { get; set; }

    public ICollection<Post> Posts { get; set; }
}

Posts コレクションの読み込みは、一括読み込み (上の「一括読み込み」を参照) または Load メソッド (下の「明示的な読み込み」を参照) を使用して実現できます。

すべてのエンティティの遅延読み込みをオフにする

コンテキスト内のすべてのエンティティに対して遅延読み込みをオフにするには、Configuration プロパティにフラグを設定します。 次に例を示します。

public class BloggingContext : DbContext
{
    public BloggingContext()
    {
        this.Configuration.LazyLoadingEnabled = false;
    }
}

関連エンティティの読み込みは、一括読み込み (上の「一括読み込み」を参照) または Load メソッド (下の「明示的な読み込み」を参照) を使用して実現できます。

明示的な読み込み

遅延読み込みを無効にした場合でも、関連エンティティを遅延読み込みできますが、明示的な呼び出しで行う必要があります。 そうするには、関連エンティティのエントリの Load メソッドを使用します。 次に例を示します。

using (var context = new BloggingContext())
{
    var post = context.Posts.Find(2);

    // Load the blog related to a given post.
    context.Entry(post).Reference(p => p.Blog).Load();

    // Load the blog related to a given post using a string.
    context.Entry(post).Reference("Blog").Load();

    var blog = context.Blogs.Find(1);

    // Load the posts related to a given blog.
    context.Entry(blog).Collection(p => p.Posts).Load();

    // Load the posts related to a given blog
    // using a string to specify the relationship.
    context.Entry(blog).Collection("Posts").Load();
}

Note

エンティティに別の単一エンティティへのナビゲーション プロパティがある場合は、Reference メソッドを使用する必要があります。 一方、エンティティに他のエンティティのコレクションへのナビゲーション プロパティがある場合は、Collection メソッドを使用する必要があります。

Query メソッドを使用すると、関連エンティティを読み込むときに Entity Framework によって使用される、基になるクエリにアクセスできます。 その後、LINQ を使用してクエリにフィルターを適用してから、ToList、Load などの LINQ 拡張メソッドを呼び出してクエリを実行できます。Query メソッドは、参照ナビゲーション プロパティとコレクション ナビゲーション プロパティの両方で使用できますが、コレクションの一部のみを読み込む際にコレクションに対して使用するのが最も便利です。 次に例を示します。

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Load the posts with the 'entity-framework' tag related to a given blog.
    context.Entry(blog)
           .Collection(b => b.Posts)
           .Query()
           .Where(p => p.Tags.Contains("entity-framework"))
           .Load();

    // Load the posts with the 'entity-framework' tag related to a given blog
    // using a string to specify the relationship.
    context.Entry(blog)
           .Collection("Posts")
           .Query()
           .Where(p => p.Tags.Contains("entity-framework"))
           .Load();
}

Query メソッドを使用する場合は、通常、ナビゲーション プロパティの遅延読み込みをオフにすることをお勧めします。 これは、そうしないと、フィルター処理されたクエリの実行前または実行後に、遅延読み込みメカニズムによってコレクション全体が自動的に読み込まれる可能性があるためです。

Note

リレーションシップは、ラムダ式の代わりに文字列として指定することもできますが、文字列を使用した場合に返される IQueryable はジェネリックではないので、それを使って役に立つ処理を行う前に、通常は Cast メソッドが必要になります。

場合によっては、データベース内の他のエンティティに関連するエンティティの数を、それらすべてのエンティティを読み込むコストを実際には発生させずに知っておくと便利であることがあります。 そのためには、Query メソッドと LINQ Count メソッドを使用できます。 次に例を示します。

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);

    // Count how many posts the blog has.
    var postCount = context.Entry(blog)
                           .Collection(b => b.Posts)
                           .Query()
                           .Count();
}