LINQ Лямбда для формирования вложенного объекта/коллекции из плоского списка, это не иерархия, а группировка похожих данных

#c# #linq

Вопрос:

Примечание: У меня нет дерева с отношением «родитель и потомок», вместо этого я пытаюсь сгруппировать аналогичный код и сохранить его вместе и сформировать вложенные объекты/коллекцию

У меня есть сценарий, в котором мои данные выровнены и я хотел бы сформировать вложенную коллекцию на основе кода и хранилища.

 public class Item 
{
   public int Code { get; set; }
   public string Price { get; set; }
   public int Store { get; set; }
   public string PriceType { get; set; }
}    

public class PriceDto
{
  public string PriceType { get; set; }
  public string Price { get; set; }
}

public class ItemVm
{
   public int Code { get; set; }
   public int Store { get; set; }
   public List<PriceDto> SalePrice { get; set; }
   public List<PriceDto> PermPrice { get; set; }
}   

public static class IEnumerableExtension
    {
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            HashSet<TKey> seenKeys = new HashSet<TKey>();
            foreach (TSource element in source)
            {
                if (seenKeys.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }
   }
 

У меня ниже приведен код для формирования вложенной коллекции из сплющенного списка.

          IList<Item> items = new List<Item>() { 
                        new Item() { Code = 1, Price = "3.83",  Store = 1202, PriceType = "Sale"} ,
                        new Item() { Code = 1, Price = "4.10",  Store = 1202, PriceType = "Perm"} ,
                        new Item() { Code = 2, Price = "3.00",  Store = 1315, PriceType = "Perm"} ,
                        new Item() { Code = 3, Price = "1.99" , Store = 1420, PriceType = "Sale"} ,
                        new Item() { Code = 3, Price = "2.25" , Store = 1420, PriceType = "Perm" } 
                    };
        
        //Distinct Code, Store amp; then form nested collection by loop
        
        var itemsList = items.Select(
         i => new ItemVm { Code = i.Code, Store = i.Store,  }
         ).DistinctBy(d => new { d.Code,d.Store }).ToList();
        
        foreach( var i in itemsList) 
        {
            i.PermPrice = items.Where(x=> x.Code == i.Code amp;amp; x.Store == i.Store amp;amp; x.PriceType == "Perm").Select(d => new PriceDto
                                                                    { Price = d.Price, PriceType = d.PriceType }).ToList();
            i.SalePrice = items.Where(x=> x.Code == i.Code amp;amp; x.Store == i.Store amp;amp; x.PriceType == "Sale").Select(d => new PriceDto
                                                                    { Price = d.Price, PriceType = d.PriceType }).ToList();
        }
 

Есть ли лучший способ, чем этот, сформировать вложенную коллекцию в одном запросе LINQ ?

Любые предложения.

Комментарии:

1. Звучит как GroupBy

2. @Ivan Steoev как бы это работало с GroupBy, так как group by получит только первую строку или только ключевые значения в группе, а не другие значения, такие как PermPrice и SalePrice, которые я получил с помощью цикла foreach

3. Хм, из мира SQL? LINQ GroupBy отличается — смотрите здесь и здесь

Ответ №1:

Вы должны использовать GroupBy

 var itemsList = items
    .GroupBy(d => new { d.Code, d.Store })
    .Select(g => new ItemVm 
    { 
        Code = g.Key.Code, 
        Store = g.Key.Store,
        PermPrice = g.Where(x => x.PriceType == "Perm")
            .Select(d => new PriceDto { Price = d.Price, PriceType = d.PriceType })
            .ToList(),
        SalePrice = g.Where(x => x.PriceType == "Sale")
            .Select(d => new PriceDto { Price = d.Price, PriceType = d.PriceType })
            .ToList()
    })
    .ToList();