'How to Sort a Model Based on a Property in IList<T> within that Model

This is a bit more complex than other questions. I have a Model that encapsulates an IList. Within each IList item there are multiple Lists. This makes it VERY difficult to flatten the entire dataset. I need to both filter and/or sort the highest IList items based on a value in that Ilist. So as an example, lets say my structure is as follows:

public class ScorecardModel
{
  //Measure Data
  public IList<ProgramScorecardModel> ProgramScorecardModel { get; set; }
                
}

public class ProgramScorecardModel
{
    public string programId { get; set; }
    public DateTime? reportingDate { get; set; }
    public string programName { get; set; }

    //Measure Data
    public IList<Sales_ViewModel> Sales_ViewModel { get; set; }
    public IList<Employees_ViewModel> Employees_ViewModel { get; set; }
    public IList<Locations_ViewModel> Locations_ViewModel { get; set; }

}

My scorecard returns all of the programs and their respective data. What I want to do is sort/filter the ProgramScorecard models based on the programId.

Here is how I call the queries that populate the scorecard.

ScorecardModel scorecard = new ScorecardModel();
scorecard = _adoSqlService.GetScorecard();

I want to call the following Sort function:

private ScorecardModel sortScorecard(ScorecardModel scorecardList, string sortField, string sortDirection)
{
    IQueryable<ScorecardModel> sortedScorecard = scorecardList.AsQueryable();
    sortedScorecard = sortedScorecard.OrderBy(sortField + " " + sortDirection);
    return sortedScorecard;
}

The problem is that the scorecardlist is not enumerable. If I change the functions to use this then the scorecard loading (GetScorecard) throws errors.

IEnumerable<ScorecardModel> scorecard = null;
scorecard = _adoSqlService.GetScorecard();

private IEnumerable<ScorecardModel> sortScorecard(IEnumerable<ScorecardModel> scorecardList, string sortField, string sortDirection)
{
  IQueryable<ScorecardModel> sortedScorecard = scorecardList.AsQueryable();
  sortedScorecard = sortedScorecard.OrderBy(sortField + " " + sortDirection);
  return sortedScorecard;
} 

I've tried changing the GetScorecard routine, but I can't get the following to work:

public IList<ScorecardModel> GetScorecard()
{
   string programid;
   string reportingdate = "2/1/2022";
   IList<ProgramViewModel> proglist = new List<ProgramViewModel>();
   proglist = GetPrograms();
   IList <ScorecardModel> scorecardData = new List<ScorecardModel>();
   scorecardData.ProgramScorecardModel = new List<ProgramScorecardModel>(); <---This Line Here, ProgramScorecardModel is not able to be a list
   int i = 0;
   foreach (var p in proglist)
   {
       ProgramScorecardModel programData = new ProgramScorecardModel();
       programData.programId = p.programId;
       programData.programName = p.programName;
       programid = p.programId;
       programData.Sales_ViewModel = new List<Sales_ViewModel>(GetSalesData(programid, reportingdate));
       programData.Employees_ViewModel = new List<Employees_ViewModel>(GetEmployeesData(programid, reportingdate));
       programData.Locations_ViewModel = new List<Locations_ViewModel>(GetLocationsData(programid, reportingdate));
       scorecardData.ProgramScorecardModel.Add(programData);
       i++;
    }
    return scorecardData;
}

How can I make the entire scorecardData and scorecardData.ProgramScorecardModel an enemrable object?

Assuming I get this working as an enumerable object, will I be able to sort AND Filter on scorecardData.ProgramScorecardModel[x].programName values?

Additional Info This is how I currently populate the scorecard model

public ScorecardModel GetScorecard()
{
    string programid;
    string reportingdate = "2/1/2022";
    IList<ProgramViewModel> proglist = new List<ProgramViewModel>();
    proglist = GetPrograms();
    ScorecardModel scorecardData = new ScorecardModel();
    scorecardData.ProgramScorecardModel = new List<ProgramScorecardModel>();
    int i = 0;
    foreach (var p in proglist)
    {
        ProgramScorecardModel programData = new ProgramScorecardModel();
        programData.programId = p.programId;
        programData.programName = p.programName;
        programid = p.programId;
        programData.Locations_ViewModel = new List<Locations_ViewModel>(GetLocationData(programid, reportingdate));
        programData.Employees_ViewModel = new List<Employees_ViewModel>(GetEmployeeData(programid, reportingdate));
        programData.Sales_ViewModel = new List<Sales_ViewModel>(GetSalesData(programid, reportingdate));

        scorecardData.ProgramScorecardModel.Add(programData);   
        i++; //This is used in some other code I have removed for simplification
    }
    return scorecardData;
}

The end result is this:
ScorecardModel
   ProgramScorecardModel[i]
       Programid
       Locations_ViewModel
       Employees_ViewModel
       Locations_ViewModel
   ProgramScorecardModel[i]
       Programid
       Locations_ViewModel
       Employees_ViewModel
       Locations_ViewModel
   ProgramScorecardModel[i]
       Programid
       Locations_ViewModel
       Employees_ViewModel
       Locations_ViewModel

Here is an oversimplification of how I'm using the scorecard model:

for (int i = 0; i < @Model.ProgramScorecardModel.Count; i++)
{
    <tr>
        <td rowspan="2" style="text-align: left; border: 1px solid black; padding-left: 5px">@Model.ProgramScorecardModel[i].programName</td>


        @{string results = Model.ProgramScorecardModel[i].Sales_ViewModel[0].rating;

            <td colspan="3" class="tdtooltip @(results == "R" ? "measure_red" : results == "Y" ? "measure_yellow" : results == "G" ? "measure_green" : results == "NR" ? "measure_nr" : results == "NS" ? "measure_ns" : "measure_na")">
                @Model.ProgramScorecardModel[i].Sales_ViewModel[0].rating
                @if (@Model.ProgramScorecardModel[i].Sales_ViewModel[0].comments.Length > 0)
                {
                    <span class="tooltiptext">@Html.Raw(Model.ProgramScorecardModel[i].Sales_ViewModel[0].comments)</span>
                }
            </td>
        }

        @{results = Model.ProgramScorecardModel[i].Employees_ViewModel[0].rating;

            <td colspan="3" class="tdtooltip @(results == "R" ? "measure_red" : results == "Y" ? "measure_yellow" : results == "G" ? "measure_green" : results == "NR" ? "measure_nr" : results == "NS" ? "measure_ns" : "measure_na")">
                @Model.ProgramScorecardModel[i].Employees_ViewModel[0].rating
                @if (@Model.ProgramScorecardModel[i].Employees_ViewModel[0].comments.Length > 0)
                {
                    <span class="tooltiptext">@Html.Raw(Model.ProgramScorecardModel[i].Employees_ViewModel[0].comments)</span>
                }
            </td>
        }

        @{ results = Model.ProgramScorecardModel[i].Locations_ViewModel[0].rating;

            <td colspan="3" class="tdtooltip @(results == "R" ? "measure_red" : results == "Y" ? "measure_yellow" : results == "G" ? "measure_green" : results == "NR" ? "measure_nr" : results == "NS" ? "measure_ns" : "measure_na")">
                @Model.ProgramScorecardModel[i].Locations_ViewModel[0].rating
                @if (@Model.ProgramScorecardModel[i].Locations_ViewModel[0].comments.Length > 0)
                {
                    <span class="tooltiptext">@Html.Raw(Model.ProgramScorecardModel[i].Locations_ViewModel[0].comments)</span>
                }
            </td>
        }
       
    </tr>
}


Solution 1:[1]

While writing the answer there are a couple things I did not understand. Maybe we can figure it out together. For now I will post what is a starting point to get things working.

using System.Linq;
using System.Collections.Generic;
                    
public class Program
{
    public static void Main()
    {
        var scorecard = GetScorecard();
        var sortedScorecard = scorecard.WithSortedPrograms("programId", "ascending");
    }
    
    public static ScorecardModel GetScorecard()
    {
        var proglist = new List<ProgramViewModel>();
        // proglist = GetPrograms();
        
        var scorecardData = new List<ProgramScorecardModel>();
        foreach (var p in proglist)
        {
          var programData = new ProgramScorecardModel();
          programData.ProgramId = p.ProgramId;
          scorecardData.Add(programData);
        }
        
        var scorecard = new ScorecardModel();
        scorecard.ProgramScorecardModel = scorecardData;
        return scorecard;
    }
}

public class ScorecardModel
{
  public List<ProgramScorecardModel> ProgramScorecardModel { get; set; }
    
  public ScorecardModel WithSortedPrograms(string sortField, string sortDirection)
  {
      var sortedModel = new ScorecardModel();
      // sortedModel.ProgramScorecardModel = ProgramScorecardModel.AsQueryable().OrderBy(sortField + " " + sortDirection);
      sortedModel.ProgramScorecardModel = ProgramScorecardModel.OrderBy(x => x.ProgramId).ToList();
      return sortedModel;
  }
}

public class ProgramScorecardModel
{
    public string ProgramId { get; set; }
}

public class ProgramViewModel
{
    public string ProgramId { get; set; }
}

I was not able to find a OrderBy method for IQueryable<T> that accepts a string. That is why for now I wrote the x => x.ProgramId expression.

There are many improvements I would personally make to your code though as I find it a bit messy. Once we find the correct solution to your problem, if you want, we can look at a different ways to approach this.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 marc_s