【问题标题】:LINQ, where third nested children's property value is equal to something?LINQ,第三个嵌套子级的属性值等于什么?
【发布时间】:2014-01-25 21:39:41
【问题描述】:

我无法让这个 LINQ 在哪里返回任何结果,我有几个这样的类:

public class RestaurantMenu
{
    public List<MenuItem> MenuItems { get; set; }
}

public class MenuItem
{
    public decimal Price { get; set; }
    public List<FoodItem> FoodItems { get; set; }
}

public class FoodItem
{
    public string Label { get; set; }
}

鉴于RestaurantMenu 的列表,我试图返回餐厅菜单的菜单项的食物项的标签与列表中的所有字符串匹配的任何匹配结果。基本上,你输入你想吃的食物,它应该返回任何有你想吃的东西的餐馆。它应该支持多个字符串,但我什至无法让它匹配一个字符串。

所以假设如下:

List<RestaurantMenu> allRestaurantMenus = /blah;
List<string> labelOfFoodsDesired = /blah;

我尝试通过像这样链接 where 表达式来做到这一点:

IQueryable<RestaurantMenu> query = allRestaurantMenus.AsQueryable();

        foreach (string foodItem in labelOfFoodItemsDesired)
        {
            query = query.Where(x => x.MenuItems.Any(y => y.FoodItems.Select(z => z.Label).Contains(foodItem)));
        }

        List<RestaurantMenu> matchingRestaurantMenus = query.ToList();

但它总是不返回任何结果,即使通过调试我确定有匹配。我该如何编写这个查询?

【问题讨论】:

  • 我认为您的链接调用有什么问题是您正在重新分配查询变量。因此,包含所有 allRestaurantMenus 的查询将设置为 null 或空列表。
  • 如果我理解正确,那么不,事实并非如此。由于可查询对象的延迟执行,我可以像上面那样构造查询,并且在调用 list 之前它不会真正执行。这与执行 query.Where(x => Id == 2).Where(x => x.Id == 3).Where(x => x.Id == 4) 之类的操作没有什么不同。它在执行之前将它们组合成一个 query.Where(x => x.Id == 2 && x.Id == 3 && x.Id == 4) 查询。并不是说那会返回任何结果,因为那是不可能的,而这只是一个例子。

标签: c# linq collections


【解决方案1】:
allRestaurantMenus.Where(m => 
    m.MenuItems.Any(mi => 
        !labelOfFoodsDesired.Except(mi.FoodItems.Select(fi => fi.Label)).Any()))

它是如何工作的:我们正在过滤至少有一个菜单项以及您传递的所有标签的菜单。与Enumerable.Except

!labelOfFoodsDesired.Except(mi.FoodItems.Select(fi => fi.Label)).Any()

我们在所需标签和所有食品标签之间产生差异。如果食品标签中存在所有需要的标签,则 set 将为空。

更新:如果您应该检查任何菜单项(不是单个),那么查询将如下所示

allRestaurantMenus.Where(m => 
    !labelOfFoodsDesired.Except(
       m.MenuItems.SelectMany(mi => mi.FoodItems.Select(fi => fi.Label))).Any())

工作原理: 方法同上,但你应该检查所有菜单项的所有标签。这是通过Enumerable.SelectMany 完成的,它将菜单项的集合扁平化为所有标签的集合。然后,如上所述,您可以在所需标签和所有菜单项的所有标签之间建立集合差异。如果 set 为空,则 menu 满足您的条件。

测试:给定以下菜单

List<RestaurantMenu> allRestaurantMenus = new List<RestaurantMenu> {
    new RestaurantMenu {
            MenuItems = new List<MenuItem> {
                new MenuItem {
                    FoodItems = new List<FoodItem> {
                        new FoodItem { Label = "Chocolate" },
                        new FoodItem { Label = "Water" }
                    }
                },
                new MenuItem {
                    FoodItems = new List<FoodItem> {
                        new FoodItem { Label = "Egg" },
                        new FoodItem { Label = "Ketchup" }
                    }
                }
            }
    },
    new RestaurantMenu {
        MenuItems = new List<MenuItem> {
                new MenuItem {
                    FoodItems = new List<FoodItem> {
                        new FoodItem { Label = "Water" }
                    }
                },
                new MenuItem {
                    FoodItems = new List<FoodItem> {
                        new FoodItem { Label = "Banana" },
                        new FoodItem { Label = "Peach" }
                    }
                }
           }
      }
 };

并遵循所需的标签

List<string> labelOfFoodsDesired = new List<string>
{
    "Water", "Banana"
};

上面的查询会将菜单展平为所有标签的序列:

{ "Chocolate", "Water", "Egg", "Ketchup" }
{ "Water", "Banana", "Peach" }

然后它将在所需标签和这个扁平化结果之间建立差异:

{ "Banana" }
{ }

因此第二个结果为空(所有需要的标签都存在于菜单中),只有第二个菜单会匹配。

【讨论】:

  • 这不是我所需要的,如果我解释得不好,我深表歉意。菜单需要包含所需的所有食物,但它不必是单个菜单项,它可以是单个菜单项,也可以是多个菜单项,只要它们都在一个菜单项中餐厅。
  • @DOTang 没问题,更新了查询。在这种情况下,您应该将所有菜单项展平为标签集合,并检查该展平集合中是否存在所有所需的标签
  • 最后一个仍然返回零结果。我正在更新我的问题以包含示例数据。
  • @DOTang 刚刚使用示例数据进行了验证(更新了我的答案) - 工作正常
【解决方案2】:

您可以使用WhereExcept-Any 的组合:

var allMatches = allRestaurantMenus
    .Where(rm => !labelOfFoodsDesired
        .Except(rm.MenuItems.SelectMany(f => f.FoodItems.Select(fi => fi.Label)))
        .Any());

labelOfFoodsDesired.Except(allmenu-food-item-labels).Any() 如果一个或多个所需标签不可用,则返回 true! 反转条件,因此它检查所有食物的可用位置。因为Except 在内部使用了一个集合,所以效率很高。

【讨论】:

    【解决方案3】:

    我认为这样的事情会奏效。我确定您可以删除 foreach 使其成为单个 linq 查询。

    foreach (var restaurant in allRestaurantMenus)
    {
        var restaurantFoodItems = restaurant.MenuItems.SelectMany(m => m.FoodItems);
        bool hasAllFoods = labelOfFoodsDesired.All(lf => restaurantFoodItems.Contains(lf));
    
        if (hasAllFoods)
            yield return restaurant;
    }
    

    【讨论】:

      【解决方案4】:
      allRestaurantMenus.Where(x => labelOfFoodsDesired.All(b => x.MenuItems.SelectMany(y => y.FoodItems.Select(a => a.Label)).Contains(b)));
      

      通过将该约束中的所有食物放入一个列表中,然后对于所有需要的食物,它会检查它们是否在该列表中。

      【讨论】:

        猜你喜欢
        • 2019-11-25
        • 1970-01-01
        • 2012-02-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多