1

So I have a list of this object returned in a working API at the moment.

    public class ShoppingListItemDto
    {
        public int ShoppingListItemId { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
    }

I want to get it into a grouped list object. Something like this:

    public class GroupedShoppingListItemDto
    {
        public string Category { get; set; }
        public List<ShoppingListItemDto> ShoppingListItemDto { get; set; }
    }

I found this post and put together the below, but I can't seem to get it to pull in the nested objects.

var list = _context.ShoppingListItems                
                .GroupBy(sli => new { sli.Category })
                     .Select(sli => new GroupedShoppingListItemDto { }).ToList();

For example, I'd like the linq query to return something like this:

{
    "category":"produce",
    "items": [
        {
            "id":1,
            "name":"lettuce",
            "category":"produce"
        },
        {
            "id":4,
            "name":"cucumber",
            "category":"produce"
        }       
    ],
    "category":"meat",
    "items": [
        {
            "id":2,
            "name":"chicken",
            "category":"meat"
        },
        {
            "id":3,
            "name":"steak",
            "category":"meat"
        }       
    ]
}   
Paul DeVito
  • 1,542
  • 3
  • 15
  • 38
  • `.Select(sli => new GroupedShoppingListItemDto { Category = sli.Key, ShoppingListItemDto = sli.ToList() }).ToList()` ? – vasily.sib May 18 '20 at 03:17
  • Doesn't look like i can do `sli.ToList()` here – Paul DeVito May 18 '20 at 03:24
  • you can call `ToList()` to fetch all records from EF, and then do grouping. Like: `_context.ShoppingListItems.ToList().GroupBy(sli => sli.Category).Select(g => new GroupedShoppingListItemDto { ... }).ToList();` – vasily.sib May 18 '20 at 04:02

2 Answers2

0

You need to ToList on Select query.

var newList = list.GroupBy(x => x.Category)
.Select(x => new GroupedShoppingListItemDto { Category = x.Key, ShoppingListItemDto = x.ToList() });

It's should works

mikenlanggio
  • 1,122
  • 1
  • 7
  • 27
  • gives me an error: (The LINQ expression '(GroupByShaperExpression: KeySelector: EntityMaterializerSource.TryReadValue(grouping.Key, 0, Property: ShoppingListItem.Category (string)), ElementSelector:(EntityShaperExpression: EntityType: ShoppingListItem ValueBufferExpression: (ProjectionBindingExpression: EmptyProjectionMember) IsNullable: False ) ) .ToList()' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable()... – Paul DeVito May 18 '20 at 03:37
  • 1
    Try using AsEnumerable() before groupBy (list.AsEnumerable()), suggested solution will work. From exception it is clear that EF is not able to translate the query. – neelesh bodgal May 18 '20 at 04:56
0

So you have a sequence of ShoppingListItems and every ShoppingListItem has a Category, which is a string. Some ShoppingListItems have the same Category.

You want to make groups of ShoppingListItems that have the same Category. You are right, for this you need to use one of the overloads of Enumerable.GroupBy.

You want to have a specific output format. In that case, use the GroupBy with a parameter ResultSelector:

var result = _context.ShoppingListItems.GroupBy(
    // parameter KeySelector: make groups of ShopplingListitems with same Category               
    shoppingListItem => shoppingListItem.Category,

    // parameter ResultSelector: for every category, and all ShoppingListItems
    // with this category make one new GroupedShoppingListItemDto
    (category, shoppingListItemsWithThisCategory) => new GroupedShoppingListItemDto
    {
        Category = category,
        ShoppingListItems = shoppingListItemsWithThisCategory.ToList(),
    })

    // execute the query:
    .ToList();

Note, that every ShoppingListItem in your GroupedShoppingListItemDto will have a Category of which you already know the value: it equals GroupedShoppingListItemDto.Category. If you have a 1000 ShoppingListItems with category "meat", you will transfer this same value more than a 1000 times, which is quite a waste of processing power.

Therefore, if you will only use the result locally within this procedure, consider using anonymous types:

var result = _context.ShoppingListItems.GroupBy(shoppingListItem => shoppingListItem.Category,

(category, shoppingListItemsWithThisCategory) => new
{
    Category = category,
    ShoppingListItems = shoppingListItemsWithThisCategory.Select(shoppingListItem => new
    {
        // Select only the properties that you plan to use:
        Id = shoppingListItem.Id,
        Name = shoppingListItem.Name,
        ...

        // No need to select, you already know the value
        // Category = shoppingListItem.Category
})
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116