Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ, where third nested children's property value is equal to something?

I am having trouble getting this LINQ where to return any results, I've got several classes like this:

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; }
}

Given a list of RestaurantMenu, I am trying to return any matching results where the restaurant menu's menu item's food item's label match ALL strings from a list. Basically, you input the food you want to eat, and it's supposed to return any restaurants where they have ALL the things you want to eat. It's supposed to support multiple strings, but I can't even get it matching one string.

So assume the following:

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

I tried doing it by chaining where expressions like so:

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();

But it always returns no results, even when through debugging I am sure there is a match. How would I write this query?

like image 532
SventoryMang Avatar asked Oct 21 '22 16:10

SventoryMang


1 Answers

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

How it works: we are filtering menus which have at least one menu item with all labels you passed. With Enumerable.Except

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

we produce set difference between desired labels and all labels of food items. If all desired labels exist in food items labels, then set will be empty.

UPDATE: If you should check any menu item (not single one) then query will look like

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

How it works: approach is same as above, but you should check all labels of all menu items. That is done with Enumerable.SelectMany which flattens collection of menu items into collection of all labels. Then, as above, you can build set difference between desired labels and all labels of all menu items. If set is empty, then menu satisfies your conditions.

TEST: Given following menus

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" }
                    }
                }
           }
      }
 };

And following desired labels

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

Query above will flatten menus to sequences of all labels:

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

And then it will build set difference between desired labels and this flattened results:

{ "Banana" }
{ }

Thus second result is empty (all desired labels exist in menu), only second menu will match.

like image 197
Sergey Berezovskiy Avatar answered Oct 24 '22 01:10

Sergey Berezovskiy