載入相關的資料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 模式來載入相關的資料。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();
}

提示

實體 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.

您可以在單一查詢中包含多個關聯性中的相關的資料。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();
}

若要包含的其中一個實體所包含的多個相關的實體。You may want to include multiple related entities for one of the entities that is being included. 例如,當查詢Blogs,包括Posts接著又想將同時包含AuthorTagsPostsFor 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 -> TagsFor example, Blog -> Posts -> Author and Blog -> Posts -> Tags. 這不表示您會收到多餘的聯結,在大部分情況下,將會合併 EF 聯結產生 SQL 時。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

您可以加入只在使用衍生的類型上定義導覽中的相關的資料IncludeThenIncludeYou 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:

  • 使用 castusing cast

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

    context.People.Include(person => (person as Student).School).ToList()
    
  • 使用的多載Include會接受參數的型別 stringusing overload of Include that takes parameter of type string

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

忽略包含Ignored includes

如果您變更查詢,使它不會再傳回查詢開始的實體類型的執行個體時,會忽略 include 運算子。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 核心會記錄警告時包含運算子都會被忽略。By default, EF Core will log a warning when include operators are ignored. 請參閱記錄檢視記錄輸出的詳細資訊。See Logging for more information on viewing logging output. 對擲回或不執行任何動作,則會忽略 include 運算子時,您可以變更行為。You can change the behavior when an include operator is ignored to either throw or do nothing. 這是通常在設定為您的內容-選項時DbContext.OnConfiguring,或在Startup.cs如果您使用 ASP.NET Core。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 核心 1.1 已引入此功能。This feature was introduced in EF Core 1.1.

您可以明確載入的導覽屬性,透過DbContext.Entry(...)應用程式開發介面。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 核心 2.1 中已引入此功能。This feature was introduced in EF Core 2.1.

若要使用消極式載入最簡單的方式是安裝Microsoft.EntityFrameworkCore.Proxies封裝,並讓它藉由呼叫UseLazyLoadingProxiesThe 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 核心然後會啟用消極式載入瀏覽的所有屬性會覆寫-也就是說,它必須是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.BlogBlog.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; }
}

沒有 proxy 的延遲載入Lazy-loading without proxies

延遲載入 proxy 的運作方式是將,以便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. 不過,它需要的參考ILazyLoader涉入 EF 核心組件的實體類型的服務。However, it requires a reference to the ILazyLoader service, which couples entity types to the EF Core assembly. 若要避免此 EF 核心允許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 核心會自動修正向上導覽屬性,您可以得到循環物件圖形中。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: 自我參考型別 '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. 這是ConfigureServices(...)方法中的Startup.csThis 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
        );

    ...
}