通过


预先加载相关数据

预先加载

可以使用该方法 Include 指定要包含在查询结果中的相关数据。 在以下示例中,结果中返回的博客将在其 Posts 属性中填充相关文章。

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

小窍门

Entity Framework Core 将自动修复以前加载到上下文实例的任何其他实体的导航属性。 即使您未显式包含某个导航属性的数据,如果之前已加载部分或所有相关实体,该属性仍可能会被填充。

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

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

注意

在单个查询中预先加载集合导航可能会导致性能问题。 有关详细信息,请参阅 “单一查询与拆分查询”。

包括多个级别

可以使用ThenInclude方法深入遍历关系,以包含多个级别的相关数据。 以下示例加载所有博客、相关文章以及每个文章的作者。

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

可以通过链接多个对ThenInclude的调用来继续包含更深层次的相关数据。

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

可以合并所有调用,以在同一查询中包含来自多个级别和多个根的相关数据。

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

你可能希望为要包含的一个实体添加多个相关的实体。 例如,在Blogs查询时,包括Posts,然后想要同时包含AuthorTags//Posts 若要包含这两者,需要指定从根目录开始的每个包含路径。 例如,Blog -> Posts -> AuthorBlog -> Posts -> Tags。 这并不意味着你将获得冗余联接;在大多数情况下,EF 会在生成 SQL 时合并联接。

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

小窍门

还可以使用单个 Include 方法加载多个导航。 这可用于导航“链”,这些“链”都是引用,或者当它们以单个集合结尾时。

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

筛选包含

在应用 Include 以加载相关数据时,可以将某些可枚举操作添加到包含的集合导航,以便筛选和排序结果。

支持的操作包括:Where、、、OrderByOrderByDescendingThenByThenByDescendingSkipTake

此类操作应应用于传递给 Include 方法的 lambda 中的集合导航,如以下示例所示:

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

每个包含的导航仅允许一个唯一的筛选操作集合。 如果为给定的集合导航应用了多个 Include 操作(blog.Posts 在下面的示例中),则只能对其中一项指定筛选器操作:

using (var context = new BloggingContext())
{
    var filteredBlogs = await 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))
        .ToListAsync();
}

或者,可以针对重复多次的每个导航应用相同的操作。

using (var context = new BloggingContext())
{
    var filteredBlogs = await 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))
        .ToListAsync();
}

注意

如果有跟踪查询,由于 导航修复,筛选后的包含结果可能会出乎意料。 以前查询并存储在更改跟踪器中的所有相关实体都将出现在筛选包含查询的结果中,即使它们不符合筛选器的要求也是如此。 考虑在这些情况下使用筛选的 Include 时使用 NoTracking 查询或重新创建 DbContext。

例:

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

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

注释

在处理跟踪查询时,应用了过滤包含的导航视为已加载。 这意味着 EF Core 不会尝试使用 显式加载延迟加载重新加载其值,即使某些元素可能仍然缺失。

包含于派生类型中

可以包含仅在派生类型 Include 上使用和 ThenInclude定义的导航中的相关数据。

给定以下模型:

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()
    
  • 使用具有 string 类型参数的 Include 重载

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

自动包括导航的模型配置

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

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

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

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

无论结果中的显示方式如何,都会对结果中返回的每个实体应用此配置。 这意味着,如果某个实体由于使用导航而包含在结果中,选择使用 Include 而非其他实体类型或自动包含配置,它将加载此实体的所有自动包含导航。 该规则同样适用于在实体的派生类型上配置为自动包含的导航。

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

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

注释

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