正确理解EFCore导航属性与Include

起因

自从接触到了导航属性这一概念后,我在Coding时遇到子实体,都会倾向于使用并添加Include,所以一直以为Include就是用来连接父子实体的。于是便出现了这种代码:

await _db.Blogs.Where(m => !m.IsDel)
		.Include(m => m.Posts.Where(n => !n.IsDel))
		.Select(m => new xxObj {
		// 若干属性,设计Blog、Posts
		})

结果测试发现,关联到的Posts子实体数据全部被查出来了,包括软删除的!我很疑惑,明明在Include中过滤掉了软删除数据,为什么结果中依然存在呢?深入了解发现,原来是我对于Include的作用理解出现了偏差。

正确理解导航属性

讲Include前,必须先了解导航属性,我此前错误的以为导航属性必须配合Include使用,这是不对的。
根据微软官方文档,导航属性分为两种:

  1. 引用导航属性(Reference Navigations)
    表示关系的"一"方,是对另一个实体的简单对象引用:
public class Post
{
	public Blog TheBlog { get; set; }  // 引用导航属性
}
  1. 集合导航属性(Collection Navigations)
    表示关系的"多"方,包含多个相关实体实例:
public class Blog
{
	public ICollection<Post> Posts { get; set; } = new List<Post>();  // 集合导航属性
}

导航属性的访问机制

导航属性的访问不总是需要Include!

  1. 在投影查询中:
// 无需Include,EF Core自动处理导航属性访问
  var blogDtos = await _db.Blogs
      .Select(m => new BlogDto
      {
          Title = m.Title,
          PostCount = m.Posts.Count(),  // 自动生成JOIN!
          Posts = m.Posts.Where(p => !p.IsDel).Select(p => new PostDto { ... }).ToArray()
      })
      .ToListAsync();

生成的SQL会自动包含必要的JOIN,无需显式Include。
2. 在实体查询中

// 返回实体对象,导航属性为null(没有Include + 没有延迟加载)
  var blogs = await _db.Blogs.ToListAsync();
  Console.WriteLine(blogs[0].Posts?.Count);  // 可能为null或0

  // 使用Include预加载导航属性
  var blogs = await _db.Blogs.Include(b => b.Posts).ToListAsync();
  Console.WriteLine(blogs[0].Posts.Count);  // 正常获取数据

正确理解Include

需要尤为注意,在投影查询中,Include会被忽略!
根据微软官方文档,Include方法的作用是"Eagerly load related data in a query"(在查询中预先加载相关数据)。
Include本质上是一种预加载优化策略,而不是访问导航属性的必要条件。
EFCore有三种数据加载策略:

  1. 预先加载(Eager Loading)- 使用Include
    var blogs = context.Blogs.Include(blog => blog.Posts).ToList();
  2. 延迟加载(Lazy Loading)- 需要特殊配置
    • 导航属性必须是virtual
    • 安装Microsoft.EntityFrameworkCore.Proxies包
    • 启用UseLazyLoadingProxies()
  3. 显式加载(Explicit Loading)- 手动加载
    context.Entry(blog).Collection(b => b.Posts).Load();

Include的使用场景

  1. 场景1:返回实体对象且需要访问导航属性
 // 正确:使用Include预加载
  public async Task<List<Blog>> GetBlogsWithPosts()
  {
      return await context.Blogs
          .Include(b => b.Posts)  // 必需!
          .ToListAsync();
  }

  // 若不安装第三方包,将获取不到子实体!不会触发额外查询
  var blogs = await GetBlogsWithPosts();
  foreach(var blog in blogs)
  {
      Console.WriteLine($"Posts count: {blog.Posts.Count}");
  }
  1. 场景2:多层级关系加载
 var blogs = context.Blogs
      .Include(blog => blog.Posts)
          .ThenInclude(post => post.Author)  // 多层级Include
      .ToList();
  1. 场景3:过滤Include(EF Core 5.0+)
// 只在返回实体时有效
  var blogs = await context.Blogs
      .Include(blog => blog.Posts.Where(p => !p.IsDel).Take(5))
      .ToListAsync();  // 注意:这里是ToListAsync(),不是Select投影
posted @ 2025-08-15 15:38  南山有榛  阅读(84)  评论(0)    收藏  举报