LINQ 分组依据和合并属性

Din*_*ino 3 c# linq

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; }
}
Run Code Online (Sandbox Code Playgroud)

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

例如:

// PrimaryId = 1
// SecondaryIds = 10, 12
Run Code Online (Sandbox Code Playgroud)

如何才能实现呢?

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

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

Cai*_*ard 7

也许我会做什么

LINQGroupBy最简单的形式采用一个 lambda,该 lambda 返回分组依据的 Key。.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);
}
Run Code Online (Sandbox Code Playgroud)

因此,它实际上将您的 Account 对象放入类似于 a 的对象中List<List<Account>>,除了内部列表有一个Key属性,该属性告诉您内部列表中的帐户全部共享什么公共值(PrimaryId)

GroupBy 有一种扩展形式,它采用第二个参数“您想要将哪些项目(可能来自原始对象)放入内部列表中?”,我们可以这样使用它:

.GroupBy(a => a.PrimaryId, a => a.SecondaryId)
Run Code Online (Sandbox Code Playgroud)

这将创建类似 a 的东西List<List<int>>,因为它只是将 secondaryId 拉出并将放入内部列表中,而不是整个帐户对象。这很好,因为您仍然可以从 获取 PrimaryId,Key并且帐户中没有其他任何内容


实际上,甚至还有另一种形式的 GroupBy 带有第三个参数,它将获取结果“列表列表”中的每个项目,并允许您对此执行某些操作..

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

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

因此,第一个参数(key,是分组键(PrimaryID),第二个参数是具有该分组键innerList)的所有 s 的列表。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() }
)
Run Code Online (Sandbox Code Playgroud)

这将能够处理辅助 ID 的单个项目列表,例如如下所示:

var accounts = new List<Account>()
{
  new Account { PrimaryId = 1, SecondaryIds = new(){10} },
  new Account { PrimaryId = 1, SecondaryIds = new(){12} }
};
Run Code Online (Sandbox Code Playgroud)

看看你的尝试:

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

这样做.GroupBy(g => g.PrimaryId == 1)将根据 PrimaryId 是否为 1 的评估进行分组。它不会将任何内容过滤为“仅 ID 1”——本质上任何 PrimaryId 1 的内容都会进入一个内部列表,而其他所有内容都会进入另一个内部列表。内部列表Key的 是一个bool. 如果您的帐户列表仅包含主 ID 为 1 的帐户对象,那么一切都会好起来的。如果没有的话那就有点乱了

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

您的带有一个参数的 GroupBy() 生成了类似 a 的内容List<List<Account>>,因此它意味着s是内部列表。它实际上IGrouping<Account>类似于 Account 对象的列表,但具有额外的 Key 属性,可以告诉您分组中所有项目的共同点。

这意味着,因为这.Selects一个s帐户列表,并且您只需要该列表中每个帐户的secondaryId ,所以您需要另一个Select,这次s从其中的每个帐户中提取 secondaryId:

.Select(s => new Accounts 
{ 
  PrimaryId = 1, 
  SecondaryIds = s.Select(acc => acc.SecondaryId)
});
Run Code Online (Sandbox Code Playgroud)

然后你想在Select上调用 ToList()

.Select(s => new Accounts 
{ 
  PrimaryId = 1, 
  SecondaryIds = s.Select(acc => acc.SecondaryId).ToList()
});
Run Code Online (Sandbox Code Playgroud)

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

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

但是第 3 个参数 GroupBy 更清晰一些..