LINQ Group By并将Group的子列表合并回唯一列表

Posted

技术标签:

【中文标题】LINQ Group By并将Group的子列表合并回唯一列表【英文标题】:LINQ Group By and merge sublist of Group back into unique list 【发布时间】:2021-12-16 12:35:01 【问题描述】:

我有这门课(为便于阅读而简化)

public class Customer

    public string Id get;set;
    public Email[] Emails get;set;

从外部系统我得到一个带有Customers 的列表,其中可以包含多个 行,用于相同 Customer (ID)

原始输入 JSON

[
id: a1, emails:[a,b,c],
id: a1, emails:[d],
id: b3, emails:[e,f],
id: k77, emails:[z,a]
]

获取客户的 c# 代码

List<Customer> dataInput = CallToExternalService(...);

我想通过 LINQ 生成一个Customers唯一 列表,其中包含所有客户电子邮件的合并 列表。 我知道如何获取唯一客户列表

dataInput.GroupBy(x => x.id).Select(x => x.First()).ToList();

但我正在努力解决如何将每个客户的电子邮件列表合并为一个。性能也是一个重要因素,因为数据将包含 10k+ 项并且需要每小时运行一次。

我尝试了很多,SelectSelectMany 是不错的候选者,但我不知道如何合并列表,更不用说将这个合并的列表带回 x.First() 项目。

元代码:

dataInput
    .GroupBy(x => x.id)
    .ForEachGroup(y => group.First().Emails = MergeLists(y.Emails;)
    .Select(z => z.First()),ToList();

预期的最终结果 C# 列表

id: a1, emails:[a,b,c,d]
id: b3, emails:[e,f]
id: k77, emails:[z,a]

【问题讨论】:

使用.SelectMany(x =&gt; x.Take(1)) 比使用.Select(x =&gt; x.First()) 更好——主要是当您修改查询时它更健壮。 所以澄清一下:您的输入可能有多个具有相同 Id 值的项目,并且它可以包括具有相似值的电子邮件,并且您希望得到一个对象列表,其中 @ 987654336@ 值是不同的,每个条目中的Email 集合仅包含与其Id 关联的不同电子邮件值? 当您说“可以包含多行”时,您的意思是“可以包含多封电子邮件”吗? @StriplingWarrior 很抱歉造成混乱。是与否。具有相同 ID 的多个项目是的。无需对电子邮件进行重复检查。 @Enigmativity 很抱歉造成混淆,只会有重复的 ID。不是电子邮件 【参考方案1】:

对“合并”的含义做出一些假设,但这看起来对吗?

dataInput
    .GroupBy(x => x.Id)
    .Select(g=> new Customer
        
            Id = g.Key,
            Emails = g.SelectMany(c => c.Emails).ToArray()
        )
    .ToList();

【讨论】:

不错!谢谢你。有没有办法不必创建新客户?客户类有很多属性,如果我不必复制每个属性,那就太好了 @David 如果CallToExternalService 正在返回一个客户列表,并且该列表有重复,并且列表中的每个Customer 实例都有很多属性,那么除了电子邮件之外的所有其他属性一样吗? @David - 你确实说过你想“生成一个独特的客户列表”。你需要更清楚你的要求。 @JackA。是的,除了电子邮件之外,所有属性都相同 @StriplingWarrior:非常感谢和点赞!【参考方案2】:

如果您确定所有其他属性都相同,您可以像在初始尝试中一样使用First 并像这样修改@StriplingWarrior 的答案:

dataInput
    .GroupBy(x => x.Id)
    .Select(g => 
    
        var customer = g.First();
        customer.Emails = g.SelectMany(c => c.Emails).ToArray();
        return customer;
    )
    .ToList();

【讨论】:

那太乱了。 同时迭代操作值通常是一件坏事。 @Enigmativity 虽然在某些情况下确实如此,但这确实有效。 试过了,效果很好!谢谢你和@StriplingWarrior。我读过 Enigmativity critique,但这是最容易阅读的,在我看来最容易维护 @David,为了您的进一步启迪,Enigmativity 试图表达的概念是建议 LINQ 查询没有副作用。这是一个讨论这个顶部的线程:***.com/questions/6386184/…。存在建议和最佳实践是有充分理由的,但有时您可能不想遵循它们。请确保您在进行交易之前充分了解您要权衡的内容。 作为 Jack 所描述的一个示例,假设有一天您将缓存添加到您获得 Customers 的数据层。更改这些客户对象会导致其他使用原始客户对象的代码路径看到更改后的合并客户对象,而不是实际来自数据存储的客户对象。【参考方案3】:

如果您要使用 Jack 的方法,我建议您使用更健壮的方法。

var intermediate =
(
    from g in dataInput.GroupBy(c => c.Id)
    from c in g.Take(1)
    select new
    
        customer = c,
        emails = g.SelectMany(d => d.Emails).ToArray()
    
)
.ToArray();
    
foreach (var x in intermediate)

    x.customer.Emails = x.emails;
;

Customer[] ouput =
    intermediate
        .Select(x => x.customer)
        .ToArray();

【讨论】:

这没有任何作用。在通过foreach 运行查询之前,您需要将查询结果分配给一个变量。另外,我建议您对其进行测试,因为它不像写的那样工作。 @JackA。 - 啊,是的,很公平。它只是在操纵一些原始元素。 @JackA。 - 已修复,但现在是相当多的代码。至少它很健壮。 操作同时迭代的问题只是操作源对象的一般问题的一个子集根本。我个人建议创建一个新对象来复制第一个客户的所有必要数据,而不是改变客户对象。 @StriplingWarrior - 哦,绝对!创建只读对象是要走的路。

以上是关于LINQ Group By并将Group的子列表合并回唯一列表的主要内容,如果未能解决你的问题,请参考以下文章

LINQ:从列表中选择项目(Group By/Select/Sum & Max!)

LINQ Group By 和选择集合

LINQ查询使用GROUP BY和Count(*)进入匿名类型

linq group by 和 select inside group by 给出错误 EFcore

linq中group by

linq中group by