包含路径表达式必须引用导航属性

时间:2018-06-11 03:53:24

标签: c# asp.net-mvc linq

我已经搜索了很多关于我的问题,但我没有找到任何明确的解决方案。我只知道我不能使用包含Include的where linq子句,但我对如何进行此查询没有任何意义。

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

实际上我想在产品上应用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();

但它没有归还所有产品而不是仅含糖的产品。

1 个答案:

答案 0 :(得分:0)

为什么您只获得糖产品

  

但它没有归还所有产品而不是仅含糖的产品。

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

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 ... )

这是(第二个)代码段的改进版本:

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仅过滤来自集合的数据。一个与另一个无关。

即使你认为做一些类似于&#34的事情也很好;只有在父母有活跃身份的情况下才包括父母"这根本不是{ {1}}旨在发挥作用 Include归结为"对于每个[type1],还会加载相关的[type2] "。这将针对您的查询将实例化的每个 [type1]对象完成,并且将加载每个相关的[type2]。

采取重构上述代码段的下一步:

Include

包含实体框架特定说明:

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

请注意它不会指示应加载哪些品牌!这是一个重要的区别。 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())
  •   

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

您可能会问自己"为什么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的原因:它允许您访问列表元素类型的属性,而不是列表本身。
在内部,EF将解构您的Select语句(这是Select),它将确定您要加载的属性。不要过分担心EF在幕后工作的方式。它并不总是漂亮。

包含/选择语法不是最漂亮的。特别是当您向下钻取多个级别时,编写(和读取)会变得很麻烦。

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

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内的元素,从而有效地防止了丑陋的语法。

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