我已经搜索了很多关于我的问题,但我没有找到任何明确的解决方案。我只知道我不能使用包含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();
但它没有归还所有产品而不是仅含糖的产品。
答案 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
内的元素,从而有效地防止了丑陋的语法。
但是还有其他原因我通常不建议在这里使用字符串参数(重命名属性时没有更新,没有智能感知,很容易出现开发人员错误)