Creating a hierarchical list from flat LINQ results

2.1k views Asked by At

Hoping someone can comment on a more effective way to do this: I have a generic list of work items that represents a user's to-do list. Currently, this is sorted and displayed in an Obout Treeview by due date only and works fine.

Now we're looking to update this by allowing users to sort their to-do list by applying a primary and secondary "filter" (i.e., by due date and then by received date or similar) to that treeview, such that the treeview displays the primary sort as the parent and the secondary sort as children. The actual items would be displayed as grandchildren, like so:

Due Date
- Received Date
-- Work Item
-- Work Item
- Received Date
-- Work Item 
Due Date

... etc

Obout Treeview has some crucial restrictions, as far as I can tell:

  1. Parent nodes must be created before their children
  2. Nodes cannot be deleted once created
  3. There is no method to see if other nodes (parent, sibling, child) exist, so you can't programmatically tell if a node would be a duplicate on the server side.

I'm modifying some old code, so be gentle with my example. I had to take out a lot to clarify what it's doing.

public void generateOboutTreeContent()
{
    // Add unique root nodes.
    switch (primarySort)
    {
        [...]
        case SortOption.ByDueDate:
            addNodesForDueDates(true);
            break;
        [...]
    }

    // Then add child nodes for each root node.
    switch (secondarySort)
    {
        [...]
        case SortOption.ByReceivedDate:
            addNodesForReceivedDates();
            break;
        [...]
    }

    // Finally, add all the actual items as grandchildren.
    foreach (WorkItem item in WorkQueue)
    {
        tree.Add(parentID, item.ID, item.url, false);
    }
}

private void addNodesForDueDates(bool isRootNode = false)
{
    var uniqueNodes = workQueue.GroupBy(i => i.DueDate).Select(group => group.First()).ToList();
    foreach (classWorkItem node in uniqueNodes)
    {
        var dueDate = node.DueDate;
        if (isRootNode)
        {
            tree.Add("root", dueDate, dueDate , false);
        }
        else
        {
            tree.Add(parentID, dueDate, dueDate, false);
        }
    }
}

How can I more effectively create the root-first hierarchy for the Obout tree from the generic list, with minimal traversing over the dataset again and again for unique values?

Creating the structure with hard-coded sorts is messy enough, but attempting to code this in a way that cleanly allows for user-defined sorts (without an explosion of subclasses or methods) has really got me stumped. I would love to hear any suggestions at all!

Thank you.

1

There are 1 answers

1
Servy On

You're not sorting the data by those dates, you're grouping the data by those data (and then sorting those groups).

To group items based on a field, simply use GroupBy.

You just need to group your items by the first field, the group each of those groups on the second field, and add in the ordering clauses as appropriate to order the groups themselves.

var query = from item in data
            group item by item.DueDate into dueDateGroup
            orderby dueDateGroup.Key
            select from item in dueDateGroup
                    group item by item.RecievedDate into recievedDateGroup
                    orderby recievedDateGroup.Key
                    select recievedDateGroup;

Or, if you prefer to use method syntax:

var query2 = data.GroupBy(item => item.DueDate)
    .OrderBy(group => group.Key)
    .Select(dueDateGroup =>
        dueDateGroup.GroupBy(item => item.RecievedDate)
            .OrderBy(group => group.Key));

Once you've transformed the data into the appropriate model, translating that model into a TreeView should be straightforward, you simply need to iterate each group and create an item for that group, then iterate the items in that group adding child nodes for each item, and do the same for those children (which are themselves groups) to add the grandchildren.