LINQ Group By 和合并属性

LINQ Group By and merge properties

private static void Main()
{
    var accounts = new List<Account>()
    {
    new Account { PrimaryId = 1, SecondaryId = 10 },
    new Account { PrimaryId = 1, SecondaryId = 12 }
    };
}

public class Account
{
    public int PrimaryId { get; set; }
    public IList<int> SecondaryIds { get; set; }
    public int SecondaryId { get; set; }
}

假设我有上面的代码,我想按PrimaryId分组,然后将SecondaryId合并到SecondaryIds中。

例如:

// PrimaryId = 1
// SecondaryIds = 10, 12

如何实现?

到目前为止我已经这样做了

var rs = accounts.GroupBy(g => g.PrimaryId == 1)
.Select(s => new Accounts 
{ PrimaryId = 1, SecondaryIds = new List<int>() { /*???*/  } });

当使用 .GroupBy() 后跟 .Select() 时,您需要在分组项目中向下钻取一层:

var rs = accounts
    .GroupBy(a => a.PrimaryId)
    .Select(gr => new Account {
        PrimaryId = gr.Key,
        SecondaryIds = gr.Select(account => account.SecondaryId).ToList()
    });

也许我会做什么

LINQ GroupBy 以其最简单的形式采用 lambda,returns 作为分组依据的键。 .GroupBy(a => a.PrimaryId)。它产生类似于“列表列表”的东西,其中内部列表中的所有内容都具有相同的键。

如果你做了那个分组,你可以这样探索它:

var listOfLists = accounts.GroupBy(a => a.PrimaryId);

foreach(var innerList in listOfLists){
  Console.WriteLine("Key is: " + innerList.Key);

  foreach(Account account in innerList)
    Console.WriteLine("Sid is: " + account.SecondaryId);
}

所以它有效地将您的帐户对象放入 List<List<Account>> 之类的东西中,除了内部列表有一个 Key 属性 告诉您帐户的共同价值(PrimaryId)在内部列表中全部共享

GroupBy 有一个扩展形式,它接受第二个参数“你想将什么项目(可能来自原始对象)放入内部列表?”,我们可以这样使用它:

.GroupBy(a => a.PrimaryId, a => a.SecondaryId)

这会创建类似 List<List<int>> 的内容,因为它不是整个帐户对象,而是将 SecondaryId 拉出并将 that 放入内部列表。这很好,因为您仍然可以从 Key 中获取 PrimaryId,并且 Account

中没有任何其他内容

实际上还有另一种形式的 GroupBy,带有 第三个 参数,它将获取结果“列表列表”中的每个项目,并允许您使用 那个..

.GroupBy(
  a => a.PrimaryId,     //derive the key
  a => a.SecondaryId,   //derive the innerList
  (key, innerList) => new Account { PrimaryId = key, SecondaryIds = innerList.ToList() }
)

您可以将第三个参数想象成您放在 groupby 末尾的 .Select()。它略有不同,并且可能更容易使用,因为“键”和“具有该键的事物列表”之间的明确区分

所以,第一个参数 (key, 是分组键(PrimaryID),第二个参数 innerList) 是所有 SecondaryId 的列表那个分组键。我们可以使用这两位信息来创建一个新的 Account 对象,其中设置了 Primary 和 SecondaryIds


我在评论中提到,在一个对象中有一个 SecondaryId 和它们的列表很奇怪。我知道你为什么这样做,但如果你想放弃 SecondaryId 单数并保持列表,groupby 会稍微改变:

.GroupBy(
  a => a.PrimaryId, 
  a => a.SecondaryId.First(),   //take the only item
  (key, innerList) => new Account { PrimaryId = key, SecondaryIds = innerList.ToList() }
)

这将能够处理次要 ID 的单个项目列表,例如这样的事情:

var accounts = new List<Account>()
{
  new Account { PrimaryId = 1, SecondaryIds = new(){10} },
  new Account { PrimaryId = 1, SecondaryIds = new(){12} }
};

看看你的尝试:

var rs = accounts.GroupBy(g => g.PrimaryId == 1)
.Select(s => new Accounts 
{ PrimaryId = 1, SecondaryIds = new List<int>() { /*???*/  } });

执行.GroupBy(g => g.PrimaryId == 1) 将根据 PrimaryId 是否为 1 的评估进行分组。它不会 filter 任何东西到“只是 ID 1” - 基本上任何 PrimaryId 1 都会进入一个内部列表,而其他一切都会进入另一个内部列表。内部列表的 Key 将是 bool。如果您的帐户列表只包含主 ID 为 1 的帐户对象,那么一切都会好起来的。如果没有它会有点失控

.Select(s => new Accounts 
{ PrimaryId = 1, SecondaryIds = new List<int>() { /*???*/  } });

你的带有一个参数的 GroupBy() 产生了类似 List<List<Account>> 的东西,所以这意味着 s 是内部列表。它实际上是一个 IGrouping<Account>,就像一个 Account 对象列表,但是有一个额外的键 属性 告诉你分组中所有项目的共同点。

这意味着,因为这个 .Select 给你 ss 是一个帐户列表,而你只需要 SecondaryId 在该列表中的每个帐户中,您需要 另一个 Select,这次 s 上拉取其中每个帐户的 SecondaryId:

.Select(s => new Accounts 
{ 
  PrimaryId = 1, 
  SecondaryIds = s.Select(acc => acc.SecondaryId)
});

然后您想在 that Select

上调用 ToList()
.Select(s => new Accounts 
{ 
  PrimaryId = 1, 
  SecondaryIds = s.Select(acc => acc.SecondaryId).ToList()
});

如果我这样做的话,我会这样做:

.GroupBy(a => a.PrimaryId)
.Select(g => new Accounts 
{ 
  PrimaryId = g.Key, 
  SecondaryIds = g.Select(acc => acc.SecondaryId).ToList()
});

但是第 3 个参数 GroupBy 更简洁一些..