Multiple keys in dictionary after group by from data table c#

c# dictionary lambda datatable

257 观看

3回复

16 作者的声誉

I am currently fetching multiple columns from excel.

Excel format is like below

 StudentId   StudentTempId    Department    Address       TotalMarks
 1           100              IT            Brooklyn      90
 1           100              IT            Manhattan     80
 2           200              HR            Boston        50

A single student can have multiple addresses that's why after fetching the data from excel into data table I am first applying group by and then converting it into a dictionary. I have applied the group by on 2 columns StudentId and StudentTempId. Because of which two keys are getting created when converting to a dictionary. For now, I have created a tuple having the same two items and rest 3 fields I am adding in the value of dictionary in the form of StudentDetail class which is as follows:

public class StudentDetail
{
    public string Department { get; set; }
    public string Address { get; set; }
    public int TotalMarks { get; set; }
}

Below code I have written to fill all the excel data in an object:

Dictionary<Tuple<int, int>, List<StudentDetail>> StudentDetailList 
      = dataTable.AsEnumerable()
      .GroupBy(row => Tuple.Create
       (
          row.Field<int>("StudentId"),
          row.Field<int>("StudentTempId")
       )).
       ToDictionary
       (
          dict => dict.Key,
          dict => dict.Select(row => new StudentDetail
          {
             Department = row.Field<string>("Department"),
             Address = row.Field<string>("Address"),
             TotalMarks = row.Field<int>("TotalMarks")
          }).ToList()
        );

Is there any other good way besides using Tuple as a combination of keys?

Any help?

作者: JohnM 的来源 发布者: 2018 年 12 月 29 日

回应 3


0

3718 作者的声誉

Use a UInt64 as the key to combine the ints of the tuple using bit shifts.

Dictionary<UInt64, List<StudentDetail>> StudentDetailList
 = dataTable.AsEnumerable()
  .GroupBy(row => new UInt64(       
      (UInt64)(row.Field<int>("StudentId"))<< 32 |
      (UInt64)(row.Field<int>("StudentTempId"));
   )).
   ToDictionary
   (
      dict => dict.Key,
      dict => dict.Select(row => new StudentDetail
      {
         Department = row.Field<string>("Department"),
         Address = row.Field<string>("Address"),
         TotalMarks = row.Field<int>("TotalMarks")
      }).ToList()
    );

Bitwise operations are intrinsic to modern processors and should yield a magnitude increase in performance(assuming a 64-bit process and no negative values). To separate the key back out, if needed, use the following code:

int StudentId = key >> 32;
int StudentTempId = key && 0xFFFFFFFF;
作者: Strom 发布者: 2018 年 12 月 29 日

0

43656 作者的声誉

Assuming you don't want the StudentId and StudentTempId as part of the StudentDetail model then I'd say it's a good approach as you can simply get the corresponding list given a key by:

var studentDetails = StudentDetailList[Tuple.Create(1, 100)];

You could make the syntax a bit nicer by grouping on ValueTuple i.e.

.GroupBy(row => (row.Field<int>("StudentId"),
                    row.Field<int>("StudentTempId")))

In which case you'd access it as:

var studentDetails = StudentDetailList[(1, 100)];

If however, you're fine with having the aforementioned properties in the StudentDetail i.e. StudentId and StudentTempId then you could get back a List<IEnumerable<StudentDetail>> by performing:

var studentDetails = dataTable.AsEnumerable()
            .GroupBy(row =>
            (
                row.Field<int>("StudentId"),
                row.Field<int>("StudentTempId")
            )).Select(g => g.Select(row => new StudentDetail
            {
                StudentId = g.Key.Item1,
                StudentTempId = g.Key.Item2,
                Department = row.Field<string>("Department"),
                Address = row.Field<string>("Address"),
                TotalMarks = row.Field<int>("TotalMarks")
            })).ToList();

Whenever you need to find a specific group of student details you can query it as:

var result = studentDetails.SingleOrDefault(s => s.Any(e => e.StudentId == 1 && e.StudentTempId == 100));

or using FirstOrDefault depending on which you find most appropriate:

var result = studentDetails.FirstOrDefault(s => s.Any(e => e.StudentId == 1 && e.StudentTempId == 100));
作者: Aomine 发布者: 2018 年 12 月 29 日

0

360 作者的声誉

Tuple is good for combined keys because its implementation of GetHashCode actually uses all of the values to generate the hash code. This is also true for ValueTuple.

What you want to avoid is using a struct without overriding GetHashCode because it will use the implementation defined in ValueType, which simply calls GetHashCode from the first value in the struct. This may cause a lot of collisions if multiple keys have the same value as their first member. This will not break your code but it will reduce the performance of the dictionary.

You can look up these implementations here: https://referencesource.microsoft.com

I suggest ValueTuple if you are using C# 7.0 or higher since there is a literal you can use for it. https://blogs.msdn.microsoft.com/mazhou/2017/05/26/c-7-series-part-1-value-tuples/

作者: Chris Rollins 发布者: 2018 年 12 月 29 日
32x32