EF6加载相关实体

[删除(380066935@qq.com或微信通知)]

更好的阅读体验请查看原文:https://docs.microsoft.com/zh-cn/ef/ef6/querying/related-data

实体框架支持三种加载相关数据的方法 - 预先加载、延迟加载和显式加载。 本主题所介绍的方法同样适用于查询使用 Code First 和 EF 设计器创建的模型。

预先加载

预先加载是指对一种实体类型的查询同时加载相关实体作为查询一部分的过程。 预先加载通过 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();
}

注意

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();
}

注意

目前无法筛选加载哪些相关实体。 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; }
}

仍可以使用预先加载(请参阅上面的预先加载)或 Load 方法(请参阅下面的显式加载)来加载 Posts 集合。

为所有实体关闭延迟加载

通过在 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();
}

注意

当实体具有指向另一个实体的导航属性时,应使用 Reference 方法。 另一方面,当实体具有指向其他实体集合的导航属性时,应使用 Collection 方法。

Query 方法提供对底层查询的访问,实体框架在加载相关实体时将使用该查询。 然后,你可以使用 LINQ 向查询应用筛选器,然后通过调用 LINQ 扩展方法(例如 ToList、Load 等)执行查询。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 方法时,通常最好为导航属性关闭延迟加载。 因为如果不这样做,在执行筛选查询之前或之后,延迟加载机制可能会自动加载整个集合。

注意

虽然可以将关系指定为字符串而不是 Lambda 表达式,但使用字符串时,返回的 IQueryable 不是泛型的,因此在使用它执行任何有用的操作之前,通常需要使用 Cast 方法。

有时,知道有多少实体与数据库中的另一个实体相关,而不会实际产生加载所有这些实体的成本,这一点很有用。 可以使用包含 LINQ Count 方法的 Query 方法来执行此操作。 例如:

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();
}