EntityFramework:包含导航 属性 与包含 Select 之间存在差异?

EntityFramework: Discrepency between Includes with Navigation Property vs. Includes with Select?

这更像是一个语法问题,而不是实际的错误或错误,因为我终于得到了我想要的工作。但我想了解并改进我目前的解决方案。

架构

假设我有一个 Users table,与 table Posts 有一对多的关系,还有一个一对一的关系Authors 的关系 table - 每个 Post.

我想编写一个自定义存储库函数来获取所有 Users、所有 Posts、每个 Author 每个 Post

尝试 #1(无效)

我想我可以做类似的事情:

public IQueryable<User> GetUsersWithPostsAndAuthors()
{
    var query = GetAll();

    // include all details on user object
    return query
        .Include(user => user.Posts.Select(x => x.Author));
}

它似乎不包含 Author 实体。实际上,我收到以下错误:

Lambda expression used inside Include is not valid.

尝试 #2(同样无效)

然后我想也许那些 Posts 需要首先在查询中,所以我尝试了这个:

public IQueryable<User> GetUsersWithPostsAndAuthors()
{
    var query = GetAll();

    // include all details on user object
    return query
        .Include(user => user.Posts)
        .Include(user => user.Posts.Select(x => x.Author)
}

不幸的是,我遇到了同样的错误:

Lambda expression used inside Include is not valid.

尝试 #3(成功!)

但是,如果我使用 Include 版本,您可以在其中提供 string navigationPropertyPath(实际上我不喜欢它,因为它只是一个硬编码字符串),像这样:

public IQueryable<User> GetUsersWithPostsAndAuthors()
{
    var query = GetAll();

    // include all details on user object
    return query
        .Include(user => user.Posts)
        .Include("Posts.Author");
}

查询按预期工作!

这是怎么回事?我认为 Select 投影与 Include 的效果相同。 (在 Whosebug 上似乎有一些答案表明了这一点。)

更重要的是,有没有一种方法可以在不对 Include 调用中的 Posts.Author 进行硬编码的情况下完成我想要的?我想在这里进行静态类型检查。

What is going on here?

无意冒犯,只是不太明白Include是干什么用的。它仅用于包含导航属性,不用于投影。

语法很清楚:

  • Include 查询根的导航属性:

    .Include(user => user.Posts)
    
  • ThenInclude 用于关闭包含的导航属性的导航属性:

    .Include(user => user.Posts).ThenInclude(p => p.Author)
    

后一个示例等同于 .Include("Posts.Author"),但由于编译时检查,lambda 语法是首选。在旧的 EF6 版本中没有 ThenInclude 并且包含更多级别的语法如您所写:.Include(user => user.Posts.Select(x => x.Author)).

投影是 LINQ 查询中的 Select,而不是 Include 语句中的投影。例如:

return query.Select(u => new { u.Id, u.Name });

预测和Includes相互排斥。在投影中,没有任何内容可以包含导航 属性。查询如:

return query
    .Include(u => u.Posts)
    .Select(u => new 
    {
        u.Id, 
        u.Name,
        Posts = u.Posts.Select(p => p.Title)
    });

将完全忽略Include。在生成的 SQL 中没有它的踪迹:只有 Post.Title 会被查询,而不是所有 Post 字段,就像 Include 会做的那样。