【问题标题】:Include Path Expression Must Refer To A Navigation Property包含路径表达式必须引用导航属性
【发布时间】:2018-06-11 03:53:24
【问题描述】:

我对我的问题进行了很多搜索,但没有找到任何明确的解决方案。我只知道我不能将 Where linq 子句与 Include 一起使用,但对我而言,我如何进行此查询没有意义。

var brands = await _context.Brands
            .Include(x => x.FoodCategories
                .Select(y => y.Products
                    .Where(z => z.Sugar)
                    .Select(w => w.FileDetail)))
            .ToListAsync();

实际上,我想在 Products 上应用 Where 语句,但我想要像我在这里做的那样层次结构中的实体。我该怎么做?
我已经尝试过使用不同的 stackoverflow 问题answer,但我不明白这一点。这是我的试验:

var brands = _context.Brands
            .Select(b => new
            {
                b,
                FoodCategories = b.FoodCategories
                    .Where(x => x.BrandId == b.BrandId)
                    .Select(c => new
                    {
                        c,
                        Products = c.Products
                            .Where(y => y.FoodCategoryId == c.FoodCategoryId &&
                                        y.Sugar)
                            .Select(p => new
                            {
                                p,
                                File = p.FileDetail
                            })
                    })
            })
            .AsEnumerable()
            .Select(z => z.b)
            .ToList();

但它并没有退回所有产品,而是仅退回糖产品。

【问题讨论】:

  • 我认为您必须使用 SelectMany 来选择实体的详细信息。
  • 您不能在包含中使用Where 语句。包含语句基于实体的结构,而Where 仅过滤集合中的数据。一个与另一个无关。即使您认为“仅当父母符合这些标准时才包括父母”之类的事情会很好,但这根本不是Include 的设计方式。包含归结为“对于每个 [type1],还加载它们相关的 [type2]”。这将针对您的查询将实例化的 every [type1] 对象完成,并且它将加载 every 相关的 [type2]。
  • 您在链接的答案中缺少以下部分 - “这仅在禁用延迟加载时有效。” 您在当前答案下发布的屏幕截图清楚地表明延迟加载处于活动状态,这会抵消您的所有努力。
  • @IvanStoev 你是美女,现在一切正常。您能否编辑 Flater 的答案。我会把它标记为我的问题的答案。

标签: c# asp.net-mvc linq


【解决方案1】:

为什么你只买糖制品

但它并没有退回所有产品,而是仅退回糖产品。

当然是。因为你要求它只给你糖制品:

var brands = _context.Brands
            .Select(b => new
            {
                b,
                FoodCategories = b.FoodCategories
                    .Where(x => x.BrandId == b.BrandId)
                    .Select(c => new
                    {
                        c,
                        Products = c.Products
                            .Where(y => y.FoodCategoryId == c.FoodCategoryId 
                                        && y.Sugar)                           //HERE!
                            .Select(p => new
                            {
                                p,
                                File = p.FileDetail
                            })
                    })
            })
            .AsEnumerable()
            .Select(z => z.b)
            .ToList();

如果您想要所有产品;然后不要只过滤 Sugar 设置为 true 的那些。


这里有很多冗余代码。

b.FoodCategories.Where(x => x.BrandId == b.BrandId)

b.FoodCategories 已经表达了这个特定品牌b 的食品类别。您不需要Where

同样适用于

c.Products.Where(y => y.FoodCategoryId == c.FoodCategoryId ... )

这是您的(第二个)sn-p 的改进版本:

var brands = _context.Brands
            .Select(b => new
            {
                b,
                FoodCategories = b.FoodCategories
                    .Select(c => new
                    {
                        c,
                        Products = c.Products
                            .Select(p => new
                            {
                                p,
                                File = p.FileDetail
                            })
                    })
            })
            .AsEnumerable()
            .Select(z => z.b)
            .ToList();

这应该更清楚地表明自定义Select 逻辑不是必需的。您所做的只是将相关实体加载到同名的属性中。您可以简单地依赖现有实体及其关系,没有理由再次定义相同的关系。

此处需要自定义 Select 的唯一原因是:

  • 您希望限制检索的列以减小数据大小(对大型查询很有用)
  • 您希望选择性地加载子项,而不仅仅是所有相关的子项。你的代码表明你想要这个,但是你说“但它没有返回所有的产品项目”所以我得出的结论是你不想过滤产品的含糖量。李>

为什么你的Include 不起作用

简单地说:您不能在包含中使用Where 语句。

Include 语句基于实体的结构,而Where 仅过滤集合中的数据。一个与另一个无关。

即使您认为“仅当父母处于活动状态时才包含父母”之类的事情会很好,但 Include 的设计初衷并非如此。
Include归结为 “对于每个 [type1],还加载它们相关的 [type2]”。这将针对您的查询将实例化的每个 [type1] 对象完成,并且它将加载每个 相关的[type2]。

下一步重构上面的sn-p:

var brands = _context.Brands
                    .Include(b => b.FoodCategories)
                    .Include(b => b.FoodCategories.Select(fc => fc.Products))
                    .Include(b => b.FoodCategories.Select(fc => fc.Products.Select(p => p.FileDetail)))
                    .ToList();

包含给实体框架的具体说明:

  • 对于每个加载的品牌,加载其相关的食品类别。
  • 对于每个加载的食品类别,加载其相关产品。
  • 对于每个加载的产品,加载其相关文件详细信息。

请注意,它并不指示应加载哪些品牌!这是一个重要的区别。 Include 语句不会以任何方式过滤数据,它们仅说明需要为将要加载的每个条目检索哪些附加数据。
将加载哪些条目尚未尚未定义。默认情况下,您会获得整个数据集,但您可以在加载数据之前使用 Where 语句进行进一步过滤。

这样想:

一家餐厅希望每位新顾客的母亲都允许为顾客提供甜点。因此,餐厅起草了一条规则:“每位顾客都必须带上他们的妈妈”。
这相当于db.Customers.Include(c => c.Mother)

这并没有说明哪些顾客可以光顾餐厅。它只规定任何顾客来餐厅必须带上他们的母亲(如果他们没有母亲,他们将带上null)。

请注意,无论有哪些顾客光顾餐厅,这条规则都适用:

  • 女士之夜:db.Customers.Include(c => c.Mother).Where(c => c.IsFemale)
  • 父母之夜:db.Customers.Include(c => c.Mother).Where(c => c.Children.Any())
  • 父亲叫 Bob night 的人:db.Customers.Include(c => c.Mother).Where(c => c.Father.Name == "Bob")

注意第三个例子。即使您对父亲进行过滤,您也只会加载母亲实体。完全可以过滤相关实体值上的项目,而无需实际加载实体本身(父亲)。


您可能会问自己“为什么是Select?”。这是个好问题,因为这里不直观。

理想情况下,你会想做类似

的事情
context.Brand.Include(b => b.FoodCategories.Products.FileDetails)

但由于语言的限制,这是不可能的。 FoodCategories 是一个 List<FoodCategory>,它没有 Products 属性。

但是,FoodCategory 本身确实具有Products 属性。这就是使用Select 的原因:它允许您访问列表元素类型的属性,而不是列表本身。
在内部,EF 将解构您的Select 语句(这是一个Expression),它会找出您要加载的属性。不要太担心 EF 在幕后的工作方式。它并不总是很漂亮。


Include/Select 语法不是最漂亮的。尤其是当您向下钻取多个级别时,编写(和读取)变得很麻烦。

所以我建议你反转你的方法(从最低的孩子开始,向上钻到父母)。从技术上讲,它产生相同的结果,但它允许使用更简洁的 Include 语法:

var brands = context.FileDetails
                    .Include(fd => fd.Product)
                    .Include(fd => fd.Product.FoodCategory)
                    .Include(fd => fd.Product.FoodCategory.Brand)
                    .Select(fd => fd.Product.FoodCategory.Brand)

现在您不需要任何讨厌的Select 解决方法来引用相关类型。

请注意,您需要为每一步添加Include!你不能只使用最后一个Include 而跳过其他的。 EF 并不推断它需要从单个 Include 加载多个关系。

请注意,只有当您拥有一对多关系链时,此技巧才真正有效。多对多关系使得应用这个技巧变得更加困难。在最坏的情况下,您将不得不使用前面示例中的Select 语法。


虽然我不喜欢采用字符串参数的 Include 方法(我不喜欢硬编码的字符串会因拼写错误而失败),但我确实觉得在这里提到它们不受此影响是相关的问题。如果您使用基于字符串的包含,您可以执行以下操作:

context.Brands
         .Include("FoodCategories")
         .Include("FoodCategories.Products")
         .Include("FoodCategories.Products.FileDetails")

字符串include方法的解析逻辑会自动寻找List里面的元素,从而有效防止丑陋的语法。

但还有其他原因,我一般不建议在这里使用字符串参数(重命名属性时不会更新,没有智能感知,很容易出现开发人员错误)

【讨论】:

猜你喜欢
  • 2019-05-21
  • 2013-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多