简体   繁体   中英

Using a IEqualityComparer for Anoymous Type in a LINQ GroupBy function

I have a IEnumerable of anonymous type as result of a LINQ join operation. Some of the values of the list are:

    { 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 }
     .
     .
     .

(The data is coming from a excel sheet) you can see that the objects with rowNumber = 0 have the column names of the table.

见Excel表格

from the spreadsheet you can notice that John (id=1) has 3 children, so I would like to group by id and have something like:

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

I assume that Linq GroupBy can do this. The problem is:

The elements of the list are of anonymous type and its properties are generic objects. CellId, CellIndex, RowNumber will always be integers so I could use cast, but CellValue is not defined, it could be string, integer, etc.

I can produce an IEnumerable of Anonymous Type <int, int, string, string, int> . I am basically converting CellId to int, CellIndex to int, CellValue to string, CellDataType to string and RowNumber to int. But I am not sure still how can I do the grouping.

How can I group them?

To compare that the Id are equals I need to look for CellIndex = 1 (which corresponds to the column name Id ) and then use the CellValue property (of the same anonymous type element) to see if it is equal.

Basically I need to group by CellValue but only for those that have a CellIndex = 1.

Any suggestions?

You have a collection of cells, but what you want is a grouping of records. Before you can get groups of records, you need to get records . How do you get records from cells?

There's a one-to-one relationship between records and rows, so you can start by grouping the cells into rows:

var rows = joinQuery
    .GroupBy(j => j.RowNumber)
    .Where(g => g.Key != 0); // Ignore the header row

Each group now represents a row, and the elements of that group are the cells. To convert those groups into records, you need to convert the cells into record fields. How do you convert cells into record fields?

There's a mapping between CellIndex and the kind of field: "1" is Id , "2" is first_name , and so on. So create a dictionary lookup from the cells:

var lookup = rows
    .Select(g => g.ToDictionary(cell => cell.CellIndex, cell => cell.CellValue));

Now that you've got a sequence of dictionaries keyed on CellIndex , take advantage of the mapping from CellIndex to fields. Handle the case where the field doesn't exist by using 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")
});

Now you have records. Last step is to group them by 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

Maybe this will help you:

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);

The main idea is to group by RowNumber first then transform your data(eg instead of just returning all cells you can create a new anonymous object which will represent your row) to something with your Id and finally group by Id .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM