为了更好地解释您代码的工作原理,让我们逐步分析一下:
var Post = await _postInterface.GetByIdAsync(id);
这一行代码从_postInterface类中获取单个帖子。这是一种常见的仓储模式设计,通常存在的一个缺点是不方便容易地获取调用者可能需要的相关数据(如预加载)。
var Tag = await _context.Posts.Include(p => p.PostTags)
.ThenInclude(t => t.Tag).ToListAsync();
这一行实际上并没有加载某个特定的标签或标签集合,而是加载了数据库中的 所有 帖子及其包含的所有标签到变量“Tag”以及DbContext的跟踪缓存中(这是非常糟糕的做法!)。
之所以看起来还能正常工作,是因为在调用这个“读取全部”的语句之后,DbContext开始跟踪所有帖子和标签,所以当你返回已经加载的“Post”时,只要访问它的PostTags/Tag代理引用,DbContext就能填充相应的数据。如果你移除那条 var Tags = ....
语句,由于DbContext不再跟踪它们,你可能会得到null值;或者,如果启用了延迟加载并且DbContext尚未释放,则在访问它们时会自动加载。
您真正想要的是这样的:
public async Task<IActionResult> Detail(int id)
{
var post = await _context.Posts
.Include(p => p.PostTags)
.ThenInclude(t => t.Tag)
.AsNoTracking()
.SingleAsync(p => p.Id == id);
return View(post);
}
这段代码告诉Entity Framework(EF)我们想要获取那个具有其关联PostTags和Tags的单一帖子。AsNoTracking()
调用是可选的,但在这种情况下会使查询速度稍快一些,因为它告诉EF无需跟踪这些实体。如果您返回的是非跟踪实体,那么Context确实不需要跟踪这些实体,因为在请求结束时Context应当会被释放。例如,如果您将.AsNoTracking()
添加到原始的var Tags = ....
查询中,您会发现代码停止工作,因为正是依赖于跟踪缓存的行为在返回“Post”时填充这些引用。