Как объединить два списка родительских / дочерних объектов с помощью LINQ при группировке по дочернему ключу - PullRequest
2 голосов
/ 28 мая 2020

Я хочу создать список CustomerOrder и сгруппировать quantity по item_id. Код почти работает, но не группируется по item_id.

Я тоже думаю что-то не так с производительностью / использованием памяти. Он работает, когда общий размер списка составляет ~ 1000, но когда он достигает ~ 30 000, возникают проблемы с памятью.

Я подозреваю, что проблема связана с «выбором нового клиента». Вероятно, мне не следует использовать ToList(), я не мог сделать это как IEnumerable. Я думал, что GroupJoin будет путем к go, но я тоже не могу заставить это работать. В примере, который я нашел с использованием группового соединения, было значение «внешнего ключа» в дочерней таблице, у меня его нет.

public class CustomerOrder
{
    public int order_id { get; set; }
    public List<OrderLine> OrderLines { get; set; }
}

public class OrderLine
{
    public int item_id { get; set; }
    public int quantity { get; set; }
}

public class Program
{
    public static void Main()
    {
        List<CustomerOrder> list1 = new List<CustomerOrder>()
        {new CustomerOrder{order_id = 1, OrderLines = new List<OrderLine>()
        {new OrderLine()
        {item_id = 123, quantity = 2}, new OrderLine()
        {item_id = 456, quantity = 3}}}};

        List<CustomerOrder> list2 = new List<CustomerOrder>()
        {new CustomerOrder{order_id = 1, OrderLines = new List<OrderLine>()
        {new OrderLine()
        {item_id = 456, quantity = 2}, new OrderLine()
        {item_id = 789, quantity = 3}}}};

        var orderdetails =
            from g in list1.Concat(list2).GroupBy(x => x.order_id) select new CustomerOrder { order_id = g.Key, OrderLines = g.SelectMany(x => x.OrderLines).ToList() };

        foreach (var item in orderdetails)
        {
            Console.WriteLine(item.order_id);
            foreach (var line in item.OrderLines)
            {
                Console.WriteLine("{0} {1}", line.item_id, line.quantity);
            }
        }
    }
}

Текущий вывод:

1
123 2
456 3
456 2
789 3

My желаемый результат:

1
123 2
456 5
789 3

Каждый список должен быть уникальным на основе order_id, но будет перекрытие в терминах order_lines. Один список почти всегда будет значительно больше другого. Коэффициент текущей ликвидности составляет 29 500: 500 для выборки 30 000.

1 Ответ

3 голосов
/ 28 мая 2020

Вам также необходимо сгруппировать свои OrderLines:

var orderdetails =
    from g in list1.Concat(list2).GroupBy(x => x.order_id) 
    select new CustomerOrder 
    { 
        order_id = g.Key,
        OrderLines = g
            .SelectMany(x => x.OrderLines)
            .GroupBy(ol => ol.item_id)
            .Select(g => new OrderLine 
            {
                item_id = g.Key, quantity = g.Sum(gg => gg.quantity)
            })
            .ToList() 
    };

Что касается производительности для обработки 30k + записей (особенно, если первая коллекция гарантированно будет иметь уникальные заказы), я думаю, лучше было бы создать словарь из первая коллекция, переключение на for / foreach l oop во второй коллекции и добавление / обновление в ней элементов словаря (включая дочерние объекты), а не создание новых.

Или хотя бы попробуйте:

var orderdetails =  list1
    .Concat(list2)
    .GroupBy(x => x.order_id)
    .Select(g => 
    {
        // may be better to materialize group, 
        // and use it for First and SelectMany
        var order = g.First();
        order.OrderLines = g
            .SelectMany(og => og.OrderLines)
            .GroupBy(ol => ol.item_id)
            .Select(olg => 
            {
                var line = olg.First();
                line.quantity = olg.Sum(ol => ol.quantity);
                return line;
            })
            .ToList();
        return order;
    })
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...