首页 > 解决方案 > 如何在对子键进行分组时使用 LINQ 加入两个父/子对象列表

问题描述

我想创建一个列表并将CustomerOrder其分组quantityitem_id该代码几乎可以工作,但它没有组合在item_id.

我也认为在性能/内存使用方面有问题。它在总列表大小约为 1000 时有效,但当它达到 ~30,000 时,它会出现内存问题。

我怀疑问题出在“选择新客户”上。我可能不应该使用ToList()IEnumerable,我不能这样做。我认为这GroupJoin将是要走的路,但我也无法让它发挥作用。我发现使用组连接的示例在子表中有一个“外键”值,我没有。

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

我想要的输出是:

1
123 2
456 5
789 3

每个列表都应该是唯一的,order_id但在order_lines. 一个列表几乎总是比另一个大得多。30K 样本的当前比率为 29,500:500。

标签: c#linq

解决方案


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循环并在其中添加/更新字典中的元素(包括子对象)而不是创建新对象。

或者至少尝试:

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

推荐阅读