相关数据的预先加载

预先加载

可以使用 Include 方法来指定要包含在查询结果中的关联数据。 在以下示例中,结果中返回的blogs将使用关联的posts填充其 Posts 属性。

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

提示

Entity Framework Core 会根据之前已加载到上下文实例中的实体自动填充导航属性。 因此,即使不显式包含导航属性的数据,如果先前加载了部分或所有关联实体,则仍可能填充该属性。

可以在单个查询中包含多个关系的关联数据。

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

注意

急于在单个查询中加载集合导航可能会导致性能问题。 有关详细信息,请参阅单个查询和拆分查询

包含多个层级

使用 ThenInclude 方法可以依循关系包含多个层级的关联数据。 以下示例加载了所有博客、其相关文章及每篇文章的作者。

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

可通过链式调用 ThenInclude,进一步包含更深级别的关联数据。

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

可以将对来自多个级别和多个根的关联数据的所有调用合并到同一查询中。

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

你可能希望将已包含的某个实体的多个关联实体都包含进来。 例如,当查询 Blogs 时,你会包含 Posts,然后希望同时包含 PostsAuthorTags。 为了包含这两项内容,需要从根级别开始指定每个包含路径。 例如,Blog -> Posts -> AuthorBlog -> Posts -> Tags。 这并不意味着会获得冗余联接查询,在大多数情况下,EF 会在生成 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 方法加载多个导航。 对于作为所有引用的导航“链”,或者当它们以单个集合结尾时,这都是可能的。

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Owner.AuthoredPosts)
        .ThenInclude(post => post.Blog.Owner.Photo)
        .ToList();
}

经过筛选的包含

在应用包含功能来加载相关数据时,可对已包含的集合导航应用某些可枚举的操作,这样就可对结果进行筛选和排序。

支持的操作包括:WhereOrderByOrderByDescendingThenByThenByDescendingSkipTake

应对传递到 Include 方法的 Lambda 中的集合导航应用这类操作,如下例所示:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(
            blog => blog.Posts
                .Where(post => post.BlogId == 1)
                .OrderByDescending(post => post.Title)
                .Take(5))
        .ToList();
}

只能对每个包含的导航执行一组唯一的筛选器操作。 如果为某个给定的集合导航应用了多个包含操作(下例中为 blog.Posts),则只能对其中一个导航指定筛选器操作:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts)
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

可对多次包含的每个导航应用相同的操作:

using (var context = new BloggingContext())
{
    var filteredBlogs = context.Blogs
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Author)
        .Include(blog => blog.Posts.Where(post => post.BlogId == 1))
        .ThenInclude(post => post.Tags.OrderBy(postTag => postTag.TagId).Skip(3))
        .ToList();
}

注意

在跟踪查询时,由于导航修正,Filtered Include 的结果可能不符合预期。 之前已查询且已存储在更改跟踪器的所有相关实体都将在 Filtered Include 查询的结果中显示,即使它们不符合筛选器的要求也是如此。 在这些情况下使用 Filtered Include 时,请考虑使用 NoTracking 查询或重新创建 DbContext。

示例:

var orders = context.Orders.Where(o => o.Id > 1000).ToList();

// customer entities will have references to all orders where Id > 1000, rather than > 5000
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();

注意

在跟踪查询的情况下,应用了经过筛选的包含的导航被视为已加载。 这意味着 EF Core 不会尝试使用显式加载延迟加载来重新加载其值,即使某些元素仍然可能缺失也不会尝试。

派生类型上的包含

可以使用 IncludeThenInclude 包含仅在派生类型上定义的导航的关联数据。

给定以下模型:

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 导航属性的内容:

  • 使用强制转换

    context.People.Include(person => ((Student)person).School).ToList()
    
  • 使用 as 运算符

    context.People.Include(person => (person as Student).School).ToList()
    
  • 使用 Include 的重载,该方法采用 string 类型的参数

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

自动包含导航的模型配置

可使用 AutoInclude 方法配置每次从数据库加载实体时要包含的模型中的导航。 这与在结果中返回实体类型的每个查询中使用导航指定 Include 具有相同的效果。 以下示例演示如何配置要自动包含的导航。

modelBuilder.Entity<Theme>().Navigation(e => e.ColorScheme).AutoInclude();

完成上述配置后,运行如下查询将为结果中的所有主题加载 ColorScheme 导航。

using (var context = new BloggingContext())
{
    var themes = context.Themes.ToList();
}

此配置应用于结果中返回的每一个实体(无论它在结果中的显示方式如何)。 这意味着,如果一个实体因为使用导航而出现在结果中,则对另一个实体类型或自动包含配置使用 Include 时,它将为其加载所有自动包含的导航。 同一规则可扩展到在实体的派生类型上配置为自动包含的导航。

如果对于特定查询,你不想通过导航加载相关数据(该导航在模型级别配置为自动包含),可在查询中使用 IgnoreAutoIncludes 方法。 使用此方法将停止加载用户配置为自动包含的所有导航。 运行如下所示的查询会从数据库返回所有主题,但不会加载 ColorScheme,即使它被配置为自动包含导航也是如此。

using (var context = new BloggingContext())
{
    var themes = context.Themes.IgnoreAutoIncludes().ToList();
}

注意

对从属类型的导航也按照约定配置为自动包含,并且使用 IgnoreAutoIncludes API 并不会阻止包含它们。 它们仍将包含在查询结果中。