関連データの読み込みLoading Related Data

Entity Framework Core を使用すると、モデル内でナビゲーション プロパティを使用して関連エンティティを読み込むことができます。Entity Framework Core allows you to use the navigation properties in your model to load related entities. 関連データの読み込みに使用される共通の O/RM パターンが 3 つあります。There are three common O/RM patterns used to load related data.

  • 一括読み込み。初期クエリの一部としてデータベースから関連データが読み込まれることを意味します。Eager loading means that the related data is loaded from the database as part of the initial query.
  • 明示的読み込み。後でデータベースから明示的に関連データが読み込まれることを意味します。Explicit loading means that the related data is explicitly loaded from the database at a later time.
  • 遅延読み込み。ナビゲーション プロパティにアクセスしたときに、データベースから透過的に関連データが読み込まれることを意味します。Lazy loading means that the related data is transparently loaded from the database when the navigation property is accessed.

ヒント

この記事のサンプルは GitHub で確認できます。You can view this article's sample on GitHub.

一括読み込みEager loading

Include メソッドを使用して、クエリ結果に含める関連データを指定することができます。You can use the Include method to specify related data to be included in query results. 次の例では、結果で返されるブログには Posts プロパティに関連する投稿が設定されます。In the following example, the blogs that are returned in the results will have their Posts property populated with the related posts.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .ToList();
}

ヒント

Entity Framework Core は、以前にコンテキスト インスタンスに読み込まれた他のエンティティに対して、ナビゲーション プロパティを自動的に修正します。Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. そのため、ナビゲーション プロパティのデータを明示的に含めていない場合でも、関連エンティティの一部またはすべてが以前に読み込まれていれば、プロパティを設定することができます。So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

複数のリレーションシップの関連データを 1 つのクエリに含めることができます。You can include related data from multiple relationships in a single query.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Include(blog => blog.Owner)
        .ToList();
}

複数のレベルを含めるIncluding multiple levels

ThenInclude メソッドを使用して、リレーションシップをドリル ダウンし、複数のレベルの関連データを含めることができます。You can drill down thru relationships to include multiple levels of related data using the ThenInclude method. 次の例では、すべてのブログ、関連記事、および各投稿の作成者を読み込みます。The following example loads all blogs, their related posts, and the author of each post.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList();
}

注意

Visual Studio の現在のバージョンでは、適切なコード補完オプションが提供されていないため、コレクションのナビゲーション プロパティのあとに ThenInclude メソッドを使用すると、正しい式に構文エラーのフラグが付けられる可能性があります。Current versions of Visual Studio offer incorrect code completion options and can cause correct expressions to be flagged with syntax errors when using the ThenInclude method after a collection navigation property. これは https://github.com/dotnet/roslyn/issues/8237 で追跡された IntelliSense のバグの症状です。This is a symptom of an IntelliSense bug tracked at https://github.com/dotnet/roslyn/issues/8237. コードが正しく、正常にコンパイルできる限り、このような見せかけ上の構文エラーは無視しても構いません。It is safe to ignore these spurious syntax errors as long as the code is correct and can be compiled successfully.

ThenInclude に対して複数の呼び出しを連鎖させて、さらなるレベルの関連データを含めることができます。You can chain multiple calls to ThenInclude to continue including further levels of related data.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
                .ThenInclude(author => author.Photo)
        .ToList();
}

このすべてを組み合わせて、複数のレベルと複数のルートの関連データを同じクエリ内に含めることができます。You can combine all of this to include related data from multiple levels and multiple roots in the same query.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
            .ThenInclude(author => author.Photo)
        .Include(blog => blog.Owner)
            .ThenInclude(owner => owner.Photo)
        .ToList();
}

含まれているエンティティの 1 つについて複数の関連エンティティを含めることができます。You may want to include multiple related entities for one of the entities that is being included. たとえば、Blog をクエリするときに、Posts を含め、さらに PostsAuthorTags の両方を含めたい場合があります。For example, when querying Blogs, you include Posts and then want to include both the Author and Tags of the Posts. この場合、ルートから始まる各インクルード パスを指定する必要があります。To do this, you need to specify each include path starting at the root. たとえば、Blog -> Posts -> AuthorBlog -> Posts -> Tags です。For example, Blog -> Posts -> Author and Blog -> Posts -> Tags. これで冗長的な結合を実現することにはならず、ほとんどの場合、SQL を生成するときに EF で結合は統合されます。This does not mean you will get redundant joins, in most cases EF will consolidate the joins when generating SQL.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Tags)
        .ToList();
}

派生型に対するインクルードInclude on derived types

IncludeThenInclude を使用して、派生型にのみ定義されているナビゲーションの関連データを含めることができます。You can include related data from navigations defined only on a derived type using Include and ThenInclude.

次のモデルがあるとします。Given the following model:

    public class SchoolContext : DbContext
    {
        public DbSet<Person> People { get; set; }
        public DbSet<School> Schools { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<School>().HasMany(s => s.Students).WithOne(s => s.School);
        }
    }

    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class Student : Person
    {
        public School School { get; set; }
    }

    public class School
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public List<Student> Students { get; set; }
    }

生徒である全ユーザーの School ナビゲーションの内容を一括して読み込むには、いくつかのパターンを使用できます。Contents of School navigation of all People who are Students can be eagerly loaded using a number of patterns:

  • キャストを使用するusing cast

    context.People.Include(person => ((Student)person).School).ToList()
    
  • as 演算子を使用するusing as operator

    context.People.Include(person => (person as Student).School).ToList()
    
  • string のパラメーターを受け取る Include のオーバーロードを使用するusing overload of Include that takes parameter of type string

    context.People.Include("Student").ToList()
    

無視されるインクルードIgnored includes

クエリが開始されたエンティティ型のインスタンスを返さないようにクエリを変更した場合、インクルード演算子は無視されます。If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.

次の例では、インクルード演算子は Blog に基づいていますが、匿名型を返すようにクエリを変更するために Select 演算子が使用されています。In the following example, the include operators are based on the Blog, but then the Select operator is used to change the query to return an anonymous type. この場合、インクルード演算子には何の効果もありません。In this case, the include operators have no effect.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .Select(blog => new
        {
            Id = blog.BlogId,
            Url = blog.Url
        })
        .ToList();
}

既定では、インクルード演算子が無視されると、EF Core では警告をログに記録します。By default, EF Core will log a warning when include operators are ignored. ログ記録の出力の表示に関する詳細については、ログ記録に関するページを参照してください。See Logging for more information on viewing logging output. インクルード演算子が無視された場合の動作を、エラーをスローするか、または何もしないかに変更できます。You can change the behavior when an include operator is ignored to either throw or do nothing. ASP.NET Core を使用している場合、通常は DbContext.OnConfiguring または Startup.cs でコンテキストのオプションを設定するときに、この変更が行われます。This is done when setting up the options for your context - typically in DbContext.OnConfiguring, or in Startup.cs if you are using ASP.NET Core.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0")
        .ConfigureWarnings(warnings => warnings.Throw(CoreEventId.IncludeIgnoredWarning));
}

明示的読み込みExplicit loading

注意

この機能は、EF Core 1.1 で導入されました。This feature was introduced in EF Core 1.1.

DbContext.Entry(...) API を使用してナビゲーション プロパティを明示的に読み込むことができます。You can explicitly load a navigation property via the DbContext.Entry(...) API.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    context.Entry(blog)
        .Collection(b => b.Posts)
        .Load();

    context.Entry(blog)
        .Reference(b => b.Owner)
        .Load();
}

また、関連エンティティを返す個別のクエリを実行することで、ナビゲーション プロパティを明示的に読み込むこともできます。You can also explicitly load a navigation property by executing a seperate query that returns the related entities. 変更の追跡が有効な場合、エンティティを読み込むと、EF Core は、既に読み込まれているエンティティがあれば参照するように、新しく読み込まれたエンティティのナビゲーション プロパティを自動的に設定します。また、新しく読み込まれたエンティティを参照するように、既に読み込まれているエンティティのナビゲーション プロパティを自動的に設定します。If change tracking is enabled, then when loading an entity, EF Core will automatically set the navigation properties of the newly-loaded entitiy to refer to any entities already loaded, and set the navigation properties of the already-loaded entities to refer to the newly-loaded entity.

また、ナビゲーション プロパティの内容を表す LINQ クエリを取得することもできます。You can also get a LINQ query that represents the contents of a navigation property.

そのため、関連エンティティをメモリに読み込まずに集計演算子を実行するなどの操作を行うことができます。This allows you to do things such as running an aggregate operator over the related entities without loading them into memory.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var postCount = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Count();
}

関連エンティティがメモリに読み込まれるようにフィルター処理することもできます。You can also filter which related entities are loaded into memory.

using (var context = new BloggingContext())
{
    var blog = context.Blogs
        .Single(b => b.BlogId == 1);

    var goodPosts = context.Entry(blog)
        .Collection(b => b.Posts)
        .Query()
        .Where(p => p.Rating > 3)
        .ToList();
}

遅延読み込みLazy loading

注意

この機能は、EF Core 2.1 で導入されました。This feature was introduced in EF Core 2.1.

遅延読み込みを使用するには、Microsoft.EntityFrameworkCore.Proxies パッケージをインストールし、UseLazyLoadingProxies を呼び出して有効にする方法が最も簡単です。The simplest way to use lazy-loading is by installing the Microsoft.EntityFrameworkCore.Proxies package and enabling it with a call to UseLazyLoadingProxies. 例:For example:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

または、AddDbContext を使用する場合:Or when using AddDbContext:

    .AddDbContext<BloggingContext>(
        b => b.UseLazyLoadingProxies()
              .UseSqlServer(myConnectionString));

EF Core は、オーバーライド可能なナビゲーション プロパティの場合に遅延読み込みを有効にします。つまり virtual であり、継承可能なクラスの ナビゲーション プロパティである必要があります。EF Core will then enable lazy-loading for any navigation property that can be overridden--that is, it must be virtual and on a class that can be inherited from. たとえば、次のエンティティでは、Post.Blog および Blog.Posts ナビゲーション プロパティの遅延読み込みが実行されます。For example, in the following entities, the Post.Blog and Blog.Posts navigation properties will be lazy-loaded.

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

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

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public virtual Blog Blog { get; set; }
}

プロキシを使用しない遅延読み込みLazy-loading without proxies

遅延読み込みプロキシは、エンティティ型コンストラクターに関するページで説明されているように、エンティティに ILazyLoader サービスを挿入することで機能します。Lazy-loading proxies work by injecting the ILazyLoader service into an entity, as described in Entity Type Constructors. 例:For example:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader?.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(ILazyLoader lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private ILazyLoader LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader?.Load(this, ref _blog);
        set => _blog = value;
    }
}

この場合、エンティティ型を継承したり、ナビゲーション プロパティを仮想にしたりする必要はありません。コンテキストにアタッチされたときに、new で作成されたエンティティ インスタンスの遅延読み込みを実行することができます。This doesn't require entity types to be inherited from or navigation properties to be virtual and allows entity instances created with new to lazy-load once attached to a context. ただし、エンティティ型を EF Core アセンブリに結合する ILazyLoader サービスを参照する必要があります。However, it requires a reference to the ILazyLoader service, which couples entity types to the EF Core assembly. これを回避するため、EF Core では ILazyLoader.Load メソッドをデリゲートとして挿入することができます。To avoid this EF Core allows the ILazyLoader.Load method to be injected as a delegate. 例:For example:

public class Blog
{
    private ICollection<Post> _posts;

    public Blog()
    {
    }

    private Blog(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public ICollection<Post> Posts
    {
        get => LazyLoader?.Load(this, ref _posts);
        set => _posts = value;
    }
}

public class Post
{
    private Blog _blog;

    public Post()
    {
    }

    private Post(Action<object, string> lazyLoader)
    {
        LazyLoader = lazyLoader;
    }

    private Action<object, string> LazyLoader { get; set; }

    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog
    {
        get => LazyLoader?.Load(this, ref _blog);
        set => _blog = value;
    }
}

上記のコードでは、デリゲートの使用をビット クリーナーにするために、拡張メソッド Load を使用しています。The code above uses a Load extension method to make using the delegate a bit cleaner:

public static class PocoLoadingExtensions
{
    public static TRelated Load<TRelated>(
        this Action<object, string> loader,
        object entity,
        ref TRelated navigationField,
        [CallerMemberName] string navigationName = null)
        where TRelated : class
    {
        loader?.Invoke(entity, navigationName);

        return navigationField;
    }
}

注意

遅延読み込みデリゲートのコンストラクター パラメーターは、"lazyLoader" と指定する必要があります。The constructor parameter for the lazy-loading delegate must be called "lazyLoader". これに別の名前を使用する構成が今後のリリースで計画されています。Configuration to use a different name this is planned for a future release.

EF Core はナビゲーション プロパティを自動的に修正するので、最終的にオブジェクト グラフの循環が生じる可能性があります。Because EF Core will automatically fix-up navigation properties, you can end up with cycles in your object graph. たとえば、ブログとその関連する投稿を読み込むと、投稿のコレクションを参照するブログ オブジェクトになります。For example, Loading a blog and it's related posts will result in a blog object that references a collection of posts. これらの各投稿には元のブログへの参照が含まれることになります。Each of those posts will have a reference back to the blog.

一部のシリアル化フレームワークでは、このような循環は許可されていません。Some serialization frameworks do not allow such cycles. たとえば、Json.NET では、循環が発生した場合に次の例外をスローします。For example, Json.NET will throw the following exception if a cycle is encountered.

Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Blog' with type 'MyApplication.Models.Blog'.Newtonsoft.Json.JsonSerializationException: Self referencing loop detected for property 'Blog' with type 'MyApplication.Models.Blog'.

ASP.NET Core を使用している場合は、オブジェクト グラフで見つかった循環を無視するように Json.NET を構成できます。If you are using ASP.NET Core, you can configure Json.NET to ignore cycles that it finds in the object graph. この操作は、Startup.csConfigureServices(...) メソッドで実行します。This is done in the ConfigureServices(...) method in Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
}