Home > OS >  Use LINQ to GroupBy two columns and build a dictionary with them?
Use LINQ to GroupBy two columns and build a dictionary with them?

Time:03-31

I am trying to use LINQ in order to build a record that is sorted by it's Store Preference and has a List of applications that are associated with each CompletionStatusFlag. In order to do this my first thought was to have the record hold a StorePreference, and then have a Dictionary that seperates the applications based on the CompletionStatus.

Employee Application

public class EmployeeApplication
{
     public int Id { get; set; }
     public StorePreference { get; set; }
     public CompletionStatusFlag CompletionStatus { get; set; }
}

ApplicationCountRecord

public class ApplicationCountRecord
{
    public Store StorePreference { get; set; }
    public Dictionary<CompletionStatusFlag, List<EmployeeApplication>> StatusApplicationPairs { get; set; } 
            = new Dictionary<CompletionStatusFlag, List<EmployeeApplication>>();
    public int TotalCount => StatusApplicationPairs.Count();
}

The problem arises when trying to create the dictionary. I have the completion status saved, but how do I get the current applications that match the completion status in order to pass them into the dictionary?

public void ConvertAppsToResults()
{
   var applications = _appRepo.Query()
       .GroupBy(x => new { x.StorePreference, x.CompletionStatus })
       .Select(y => new ApplicationCountRecord()
       {
            StorePreference = y.Key.StorePreference,
            //Somehow create dictionary here.
       });
}

Heres is another incorrect attempt that wont even compile, but it might help you see my thought process.

public void ConvertAppsToResults()
{
    //get apps in DateFilter range, and creates records seperated by StorePreference, and CompletionStatus
    var applications = _applicationRepo.Query()
                .GroupBy(x => new { x.StorePreference1, x.CompletionStatus })
                .Select(y => new ApplicationCountRecord()
                {
                    StorePreference = y.Key.StorePreference,
                    StatusApplicationPairs = new Dictionary<CompletionStatusFlag, List<EmployeeApplication>>()
                    .Add(y.Key.CompletionStatus, y.Where(x => x.CompletionStatus == y.Key.CompletionStatus).ToList());
                });
}

CodePudding user response:

There's a double grouping going on here, which is possibly the source of your confusion

employeeApplications
  .GroupBy(ea => ea.Store)
  .Select(g => new ApplicationCountRecord()
    {
      StorePreference = g.Key
      StatusApplicationPairs = g.GroupBy(ea => ea.CompletionStatus).ToDictionary(g2 => g2.Key, g2 => g2.ToList())
    }
  )

Suppose you have 100 EmployeeApplications, across 10 Stores, and there are 5 statuses and 2 applications in each status. 2 apps * 5 statuses * 10 stores = 100 applications

GroupBy(Store) takes your list of e.g. 100 EmployeeApplications (10 per store) and groups it into 10 IGrouping that are conceptually each a list of 10 EmployeeApplications. 10 IGrouping for Store1, 10 IGrouping for Store2 etc..

Select runs over the 10 groupings, and on each one g (which, remember, behaves like a list of EmployeeApplications that all have the same Store, given in g.Key) it groups again by calling GroupBy(CompletionStatus) to further subdivide the 10 EAs in g on CompletionStatus. The 10 EAs for e.g. "Store 1" are divvied up into 5 IGroupings (5 statuses) that have 2 EAs in each inner grouping, then the same process is done for Store 2, 3 etc. There are thus 5 g2.Keys, one for each of the 5 statuses and there are 2 EAs in each g2

ToDictionary is called after it's grouped, so you get a dictionary with 5 keys and each key relates to a list of 2 applications. The ToDictionary takes two arguments; what to use for the key (g2.Key is a Status) and what to use for the value (g2.ToList() realizes a List<EmployeeApplication>)


= new Dictionary<CompletionStatusFlag, List<EmployeeApplication>>(); is unnecessary in AppCountRecord, as it will be replaced anyway

  • Related