使用Linq来合并List<T>?

3

我想知道是否有一种方法可以用LINQ来合并订单对象列表?我的情况如下,为了这个问题的简便,我已经压缩了它。

public class Order
{
    public string Guid { get; set; }
    public string Parent { get; set; }
    public string ProductID { get; set; }
    public string Description { get; set; }
    public double Quantity { get; set; }
    public double Total { get; set; }
}

根据上面的内容,这将变成以下列表:
GUID       PARENT       ID         Desc          Qty           Total
====================================================================
aaa                     120        Burger         1            2.50
bbb        aaa          121        + Bacon        1            0.50
ccc                     123        Fries          1            1.50
ddd                     120        Burger         1            2.50
eee        ddd          124        + Cheese       1            0.30
fff                     123        Fries          1            1.50

我希望在这个例子中实现以下目标:最终得到如下结果。
GUID       PARENT       ID         Desc          Qty           Total
====================================================================
aaa                     120        Burger         1            2.50
bbb        aaa          121        + Bacon        1            0.50
ccc                     123        Fries          2            3.00
ddd                     120        Burger         1            2.50
eee        ddd          124        + Cheese       1            0.30

我不打算合并具有子产品的项目,而是对其他所有内容进行汇总总数和数量。

我目前的想法是创建一个仅包含没有子项的产品的单独列表,并将其与具有子项的产品进行合并。

我可以这样做,但我想知道是否有更清晰的方法,任何意见都将非常感谢。


寻找Concat?https://msdn.microsoft.com/zh-cn/library/bb386979(v=vs.100).aspx - Ya Wang
1
为什么你在添加一个项目时不加数量?首先检查该项目是否已经在列表中,如果是,则将数量加1。 - Spluf
2
我同意@Spluf的观点 - 这不是最适合你的数据的模式。那不是一个订单 - 它是一个订单项目。列表是订单,但听起来它似乎没有提供给你想要的数据的接口。 - Conduit
假设GUID对于每个项目类型都是唯一的,您最好将Dictionary<GUID, count>作为您的顺序保留。更好的方法是 - 封装字典并提供良好的命名方法 - AddItem,GetItemCount,... - Conduit
在理想的情况下,我会这样做,但不幸的是,我受限于一个第三方应用程序,我无法控制它。@spluf - JazziJeff
@JazziJeff 好耶 -_- - Conduit
3个回答

3
这将使带有子订单的订单保持分离,并仅合并独立订单:
var orders = originalOrders.GroupBy(_ => _.parent ? _.parent : _.guid);
var consolidated = orders
    .Where(grp => grp.Count() == 1) // only orders with no children
    .SelectMany(grp => grp)
    .GroupBy(_ => _.ID)
    .Select(grp => grp.Aggregate((a,b) => new order
        {
            guid = a.guid,
            parent = a.parent,
            ProductID = a.ProductID,
            Description = a.Description,
            Quantity = a.Quantity + b.Quantity,
            Total = a.Total + b.Total
        }
));

var allOrders = orders
    .Where(grp => grp.Count() > 1)
    .SelectMany(grp => grp)
    .Concat(consolidated);

1
这看起来还不错,但我认为你漏掉了一个条件:如果该项有子产品,则不要尝试合并 - Cyril Gandon
嗨,Mike - 很抱歉现在才回复你。你的代码 var orders = originalOrders.GroupBy(_ => _.parent ? _.parent : _.guid); 在编译时给出了一个“无法隐式转换类型字符串为布尔值”的错误提示,请问你有什么建议吗?详情请见 https://dotnetfiddle.net/FyIfZV - JazziJeff
这个想法是,如果有父GUID,则使用父GUID,否则使用订单自己的GUID。例如,如果没有父级订单,则可以执行 _ => _.parent == null ? _.guid : _.parent,或者如果它是一个空字符串,则可以执行:_ => string.IsNullOrEmpty(_.parent) ? _.guid : _.parent - Mike Jerred
感谢您的快速回复-我明白了并且实现了string.isnullorempty,但是在第二个select many中仍然遇到了困难,因为方法的类型参数无法从使用中推断出来-the link above in my comment has been updated to show this. - JazziJeff
抱歉!应该是 Select 而不是 SelectMany,我会编辑我的答案。 - Mike Jerred
显示剩余3条评论

2
如果您想一次完成此操作,关键是计算出可以分组的内容。 由于我们不想将具有子项的项目分组,因此我们需要为这些项目找到唯一的分组依据。如果我们考虑两个汉堡行,它们之间唯一不同的是Guid,因此Guid需要成为我们分组的依据。如果该项没有子行,则应将其分组。 因此,我们计算了一个分组子句,如果该项具有子项,则该子句变为Guid,否则为空字符串。 然后,还需要按ProductID进行分组,因为这是我们要对没有子项的项目进行分组的依据。
var lst = orders.GroupBy(x => new
{
    x.ProductID,
    GuidIfHaveChildren = (orders.Any(x2 => x2.Parent == x.Guid) ? x.Guid : ""),
    x.parent
}).Select(x => new
 {
     Guid = x.First().Guid,
     Parent = x.First().Parent,
     Id = x.Key.ProductID,
     Desc = x.First().Description,
     Qty = x.Sum(p => p.Quantity),
     Total = x.Sum(p => p.Total)
 });

非常接近,但似乎将子产品与父产品分组。例如,如果我销售1个汉堡包+培根,2个薯条,1个汉堡包加培根,则输出为1个汉堡包+ 2个培根,2个薯条,1个汉堡包-理想情况下,它应该将培根与每个汉堡包保持在一起,如果这有意义的话。 - JazziJeff
你能否更新示例数据,以包含这种情况?这样我就可以理解正确了。 - fhogberg
在这里快速测试一下你的答案:https://dotnetfiddle.net/S6WhNW,谢谢。 - JazziJeff
太完美了,我现在只是在测试一些边缘情况,但真的很棒。 - JazziJeff

1
from o in orders
group o by o.ID into g
from gg in g select new Order
{
        guid = gg.guid, parent = gg.parent, ProductId = gg.ProductId, Description = gg.Description,
        Quantity = orders.Any(od=> od.parent != null || od.parent == gg.guid) 
                        ? gg.Quantity : g.Sum(ggg=>ggg.Quantity),
        Total = orders.Any(od=> od.parent != null || od.parent == gg.guid) 
                        ? gg.Total : g.Sum(ggg=>ggg.Total)
}

这可能会存在一些性能问题,但这应该可以帮助您入门。


谢谢,我似乎在从gg中选择新订单时遇到了一个问题,请参见https://dotnetfiddle.net/S6WhNW,如果你有时间。 - JazziJeff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接