正确理解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使用,这是不对的。
根据微软官方文档,导航属性分为两种:
- 引用导航属性(Reference Navigations)
表示关系的"一"方,是对另一个实体的简单对象引用:
public class Post
{
public Blog TheBlog { get; set; } // 引用导航属性
}
- 集合导航属性(Collection Navigations)
表示关系的"多"方,包含多个相关实体实例:
public class Blog
{
public ICollection<Post> Posts { get; set; } = new List<Post>(); // 集合导航属性
}
导航属性的访问机制
导航属性的访问不总是需要Include!
- 在投影查询中:
// 无需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有三种数据加载策略:
- 预先加载(Eager Loading)- 使用Include
var blogs = context.Blogs.Include(blog => blog.Posts).ToList(); - 延迟加载(Lazy Loading)- 需要特殊配置
- 导航属性必须是virtual
- 安装Microsoft.EntityFrameworkCore.Proxies包
- 启用UseLazyLoadingProxies()
- 显式加载(Explicit Loading)- 手动加载
context.Entry(blog).Collection(b => b.Posts).Load();
Include的使用场景
- 场景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}");
}
- 场景2:多层级关系加载
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author) // 多层级Include
.ToList();
- 场景3:过滤Include(EF Core 5.0+)
// 只在返回实体时有效
var blogs = await context.Blogs
.Include(blog => blog.Posts.Where(p => !p.IsDel).Take(5))
.ToListAsync(); // 注意:这里是ToListAsync(),不是Select投影

浙公网安备 33010602011771号