在 LINQ GroupBy 函数中使用匿名类型的 IEqualityComparer
Using a IEqualityComparer for Anoymous Type in a LINQ GroupBy function
作为 LINQ 连接操作的结果,我有一个 匿名类型 的 IEnumerable
。列表的一些值是:
{ CellId = 0, CellIndex = "1", CellDataType = "String", CellValue = "Id", RowNumber = 0 }
{ CellId = 1, CellIndex = "2", CellDataType = "String", CellValue = "first_name", RowNumber = 0 }
{ CellId = 2, CellIndex = "3", CellDataType = "String", CellValue = "age", RowNumber = 0 }
{ CellId = 3, CellIndex = "4", CellDataType = "String", CellValue = "child_name", RowNumber = 0 }
{ CellId = 4, CellIndex = "5", CellDataType = "String", CellValue = "child_age", RowNumber = 0 }
{ CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "1", RowNumber = 1 }
{ CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "john", RowNumber = 1 }
.
.
.
(数据来自excel sheet)你可以看到rowNumber = 0的objects有table的列名。
从传播sheet你可以注意到 John (id=1) 有 3 children,所以我想按 id 分组并有类似的东西:
Id = 1
first_name = "john", age = 30, child_name = "Andy", child_age = 4
first_name = "john", age = 30, child_name = "Anna", child_age = 6
first_name = "john", age = 30, child_name = "Lily", child_age = 8
Id = 2
first_name = "Emily", age = 32, child_name = "Harry", child_age = 3
first_name = "Emily", age = 32, child_name = "David", child_age = 3
Id = 3
first_name = "Peter", age = 40, child_name = "Carol", child_age = 2
我假设 Linq GroupBy 可以做到这一点。问题是:
列表的元素是 anonymous type 并且它的属性是通用的 objects。 CellId、CellIndex、RowNumber 将始终是整数,因此我可以使用强制转换,但未定义 CellValue,它可以是字符串、整数等。
我可以制作一个IEnumerable of Anonymous Type <int, int, string, string, int>
。我基本上是将 CellId 转换为 int,将 CellIndex 转换为 int,将 CellValue 转换为字符串,将 CellDataType 转换为字符串,将 RowNumber 转换为 int。但我仍然不确定如何进行分组。
如何将它们分组?
为了比较 Id 是否相等,我需要查找 CellIndex = 1(对应于列名 Id),然后使用 CellValue 属性(相同的匿名类型元素)看是否相等。
基本上我需要按 CellValue 分组,但仅适用于 CellIndex = 1 的那些。
有什么建议吗?
也许这会对您有所帮助:
var list = new [] {
new { CellId = 0, CellIndex = "1", CellDataType = "String", CellValue = "Id", RowNumber = 0 },
new { CellId = 1, CellIndex = "2", CellDataType = "String", CellValue = "first_name", RowNumber = 0 },
new { CellId = 2, CellIndex = "3", CellDataType = "String", CellValue = "age", RowNumber = 0 },
new { CellId = 3, CellIndex = "4", CellDataType = "String", CellValue = "child_name", RowNumber = 0 },
new { CellId = 4, CellIndex = "5", CellDataType = "String", CellValue = "child_age", RowNumber = 0 },
new { CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "1", RowNumber = 1 },
new { CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "john", RowNumber = 1 },
new { CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "1", RowNumber = 2 },
new { CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "john", RowNumber = 2 },
new { CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "2", RowNumber = 3 },
new { CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "emily", RowNumber = 3 },
};
var result = list
.GroupBy(x => x.RowNumber)
//.Where(x => x.Key > 0)//in case you want to skip you header row
.Select(x => new {
Id = x.SingleOrDefault(t => t.CellIndex == "1").CellValue,
first_name = x.SingleOrDefault(t => t.CellIndex == "2")?.CellValue,
age = x.SingleOrDefault(t => t.CellIndex == "3")?.CellValue,
child_name = x.SingleOrDefault(t => t.CellIndex == "4")?.CellValue,
child_age = x.SingleOrDefault(t => t.CellIndex == "5")?.CellValue
})
.GroupBy(x => x.Id);
主要思想是先按 RowNumber
分组,然后将数据转换为 Id
最后按 Id
.
分组
您有一组单元格,但您想要的是一组记录。在获取 groups 条记录之前,您需要先获取 records。如何从单元格中获取记录?
记录和行之间存在 one-to-one 关系,因此您可以从将单元格分组到行开始:
var rows = joinQuery
.GroupBy(j => j.RowNumber)
.Where(g => g.Key != 0); // Ignore the header row
现在每个组代表一行,该组的元素是单元格。要将这些组转换为记录,您需要将单元格转换为记录字段。如何将单元格转换为记录字段?
CellIndex
和字段类型之间存在映射:“1”是Id
,“2”是first_name
,依此类推。因此,从单元格创建字典查找:
var lookup = rows
.Select(g => g.ToDictionary(cell => cell.CellIndex, cell => cell.CellValue));
现在您已经有了一系列以 CellIndex
为关键字的字典,利用从 CellIndex
到字段的映射。使用 GetValueOrDefault
:
处理字段不存在的情况
var records = lookup.Select(l => new
{
Id = l.GetValueOrDefault("1"),
first_name = l.GetValueOrDefault("2"),
age = l.GetValueOrDefault("3"),
child_name = l.GetValueOrDefault("4"),
child_age = l.GetValueOrDefault("5")
});
现在你有记录了。最后一步是按 Id
:
对它们进行分组
var groups = records.GroupBy(r => r.Id).ToArray();
foreach (var group in groups)
{
Console.WriteLine($"Id = {group.Key}");
foreach (var record in group)
{
Console.WriteLine($" first_name = {record.first_name}, age = {record.age}, child_name = {record.child_name}, child_age = {record.child_age}");
}
Console.WriteLine();
}
// Outputs:
Id = 1
first_name = john, age = 30, child_name = Andy, child_age = 4
first_name = john, age = 30, child_name = Anna, child_age = 6
first_name = john, age = 30, child_name = Lily, child_age = 8
Id = 2
first_name = Emily, age = 32, child_name = Harry, child_age = 3
first_name = Emily, age = 32, child_name = David, child_age = 3
Id = 3
first_name = Peter, age = 40, child_name = Carol, child_age = 2
作为 LINQ 连接操作的结果,我有一个 匿名类型 的 IEnumerable
。列表的一些值是:
{ CellId = 0, CellIndex = "1", CellDataType = "String", CellValue = "Id", RowNumber = 0 }
{ CellId = 1, CellIndex = "2", CellDataType = "String", CellValue = "first_name", RowNumber = 0 }
{ CellId = 2, CellIndex = "3", CellDataType = "String", CellValue = "age", RowNumber = 0 }
{ CellId = 3, CellIndex = "4", CellDataType = "String", CellValue = "child_name", RowNumber = 0 }
{ CellId = 4, CellIndex = "5", CellDataType = "String", CellValue = "child_age", RowNumber = 0 }
{ CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "1", RowNumber = 1 }
{ CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "john", RowNumber = 1 }
.
.
.
(数据来自excel sheet)你可以看到rowNumber = 0的objects有table的列名。
从传播sheet你可以注意到 John (id=1) 有 3 children,所以我想按 id 分组并有类似的东西:
Id = 1
first_name = "john", age = 30, child_name = "Andy", child_age = 4
first_name = "john", age = 30, child_name = "Anna", child_age = 6
first_name = "john", age = 30, child_name = "Lily", child_age = 8
Id = 2
first_name = "Emily", age = 32, child_name = "Harry", child_age = 3
first_name = "Emily", age = 32, child_name = "David", child_age = 3
Id = 3
first_name = "Peter", age = 40, child_name = "Carol", child_age = 2
我假设 Linq GroupBy 可以做到这一点。问题是:
列表的元素是 anonymous type 并且它的属性是通用的 objects。 CellId、CellIndex、RowNumber 将始终是整数,因此我可以使用强制转换,但未定义 CellValue,它可以是字符串、整数等。
我可以制作一个IEnumerable of Anonymous Type <int, int, string, string, int>
。我基本上是将 CellId 转换为 int,将 CellIndex 转换为 int,将 CellValue 转换为字符串,将 CellDataType 转换为字符串,将 RowNumber 转换为 int。但我仍然不确定如何进行分组。
如何将它们分组?
为了比较 Id 是否相等,我需要查找 CellIndex = 1(对应于列名 Id),然后使用 CellValue 属性(相同的匿名类型元素)看是否相等。
基本上我需要按 CellValue 分组,但仅适用于 CellIndex = 1 的那些。
有什么建议吗?
也许这会对您有所帮助:
var list = new [] {
new { CellId = 0, CellIndex = "1", CellDataType = "String", CellValue = "Id", RowNumber = 0 },
new { CellId = 1, CellIndex = "2", CellDataType = "String", CellValue = "first_name", RowNumber = 0 },
new { CellId = 2, CellIndex = "3", CellDataType = "String", CellValue = "age", RowNumber = 0 },
new { CellId = 3, CellIndex = "4", CellDataType = "String", CellValue = "child_name", RowNumber = 0 },
new { CellId = 4, CellIndex = "5", CellDataType = "String", CellValue = "child_age", RowNumber = 0 },
new { CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "1", RowNumber = 1 },
new { CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "john", RowNumber = 1 },
new { CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "1", RowNumber = 2 },
new { CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "john", RowNumber = 2 },
new { CellId = 5, CellIndex = "1", CellDataType = "Number", CellValue = "2", RowNumber = 3 },
new { CellId = 6, CellIndex = "2", CellDataType = "String", CellValue = "emily", RowNumber = 3 },
};
var result = list
.GroupBy(x => x.RowNumber)
//.Where(x => x.Key > 0)//in case you want to skip you header row
.Select(x => new {
Id = x.SingleOrDefault(t => t.CellIndex == "1").CellValue,
first_name = x.SingleOrDefault(t => t.CellIndex == "2")?.CellValue,
age = x.SingleOrDefault(t => t.CellIndex == "3")?.CellValue,
child_name = x.SingleOrDefault(t => t.CellIndex == "4")?.CellValue,
child_age = x.SingleOrDefault(t => t.CellIndex == "5")?.CellValue
})
.GroupBy(x => x.Id);
主要思想是先按 RowNumber
分组,然后将数据转换为 Id
最后按 Id
.
您有一组单元格,但您想要的是一组记录。在获取 groups 条记录之前,您需要先获取 records。如何从单元格中获取记录?
记录和行之间存在 one-to-one 关系,因此您可以从将单元格分组到行开始:
var rows = joinQuery
.GroupBy(j => j.RowNumber)
.Where(g => g.Key != 0); // Ignore the header row
现在每个组代表一行,该组的元素是单元格。要将这些组转换为记录,您需要将单元格转换为记录字段。如何将单元格转换为记录字段?
CellIndex
和字段类型之间存在映射:“1”是Id
,“2”是first_name
,依此类推。因此,从单元格创建字典查找:
var lookup = rows
.Select(g => g.ToDictionary(cell => cell.CellIndex, cell => cell.CellValue));
现在您已经有了一系列以 CellIndex
为关键字的字典,利用从 CellIndex
到字段的映射。使用 GetValueOrDefault
:
var records = lookup.Select(l => new
{
Id = l.GetValueOrDefault("1"),
first_name = l.GetValueOrDefault("2"),
age = l.GetValueOrDefault("3"),
child_name = l.GetValueOrDefault("4"),
child_age = l.GetValueOrDefault("5")
});
现在你有记录了。最后一步是按 Id
:
var groups = records.GroupBy(r => r.Id).ToArray();
foreach (var group in groups)
{
Console.WriteLine($"Id = {group.Key}");
foreach (var record in group)
{
Console.WriteLine($" first_name = {record.first_name}, age = {record.age}, child_name = {record.child_name}, child_age = {record.child_age}");
}
Console.WriteLine();
}
// Outputs:
Id = 1
first_name = john, age = 30, child_name = Andy, child_age = 4
first_name = john, age = 30, child_name = Anna, child_age = 6
first_name = john, age = 30, child_name = Lily, child_age = 8
Id = 2
first_name = Emily, age = 32, child_name = Harry, child_age = 3
first_name = Emily, age = 32, child_name = David, child_age = 3
Id = 3
first_name = Peter, age = 40, child_name = Carol, child_age = 2