【问题标题】:Using a IEqualityComparer for Anoymous Type in a LINQ GroupBy function在 LINQ GroupBy 函数中对匿名类型使用 IEqualityComparer
【发布时间】:2019-06-13 23:31:53
【问题描述】:

我有一个 匿名类型IEnumerable 作为 LINQ 连接操作的结果。该列表的一些值是:

    { 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表格)你可以看到rowNumber = 0的对象有表格的列名。

从电子表格中您可以注意到 John (id=1) 有 3 个孩子,所以我想按 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 类型,其属性是通用对象。 CellId、CellIndex、RowNumber 将始终是整数,因此我可以使用强制转换,但未定义 CellValue,它可以是字符串、整数等。

我可以生成IEnumerable of Anonymous Type <int, int, string, string, int>。我基本上是将 CellId 转换为 int,将 CellIndex 转换为 int,将 CellValue 转换为 string,将 CellDataType 转换为 string,将 RowNumber 转换为 int。但我仍然不确定如何进行分组。

如何将它们分组?

要比较 Id 是否相等,我需要查找 CellIndex = 1(对应于列名 Id),然后使用 CellValue 属性(相同的匿名类型元素)查看如果相等。

基本上我需要按 CellValue 分组,但只针对 CellIndex = 1 的那些。

有什么建议吗?

【问题讨论】:

  • 如果不实现IEqualityComparer<Something>,就无法拥有相等比较器。 Something 不能是匿名类型。您需要将该匿名类型映射到某个已定义的类型。
  • 为什么不直接定义一个类型呢?看起来你在浪费很多时间试图让匿名类型工作,而写一个课程需要五分钟......

标签: c# .net linq anonymous-types iequalitycomparer


【解决方案1】:

您有一组单元格,但您想要的是一组记录。在获得记录之前,您需要获得记录。如何从单元格中获取记录?

记录和行之间存在一对一的关系,因此您可以先将单元格分组为行:

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

【讨论】:

    【解决方案2】:

    也许这会对你有所帮助:

    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.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多