3

I've been looking through the search system and not come across an answer for my problem, while these questions are similar and I even used some of them to make a start I've hit a brick wall.

I've already checked out these questions

I have an object brought back by nHibernate called Person as follows

 public class Person : IPerson, IComparable<Person>{

    // private properties
    private int _id;
    private string _name;
    private Person _parent;
    private IList<Person> _children;
    private IList<Group> _groups;

    // public properties
    public virtual int id
    {
       get { return _id; }
       set { _id = value; }
    }

    public virtual string name
    {
       get { return _name; }
       set { _name= value; }
    }

    public virtual Person Parent
    {
       get { return _parent; }
       set { _parent = value; }
    }

    public virtual IList<Person> Children
    {
       get { return _children; }
       set { _children = value; }
    }

    public virtual IList<Group> Groups
    {
       get { return _groups; }
       set { _groups = value; }
    }

    // constructor
    public Person() {}

    // this section I added after looking on SO
    public virtual Int32 parentid
    {
       get {
          if (_parent == null)
          {
             return _id;
          } else {
             return _parent.id;
          }
       }
    }

    public virtual Int32 CompareTo(Person obj)
    {
       if (this.id == obj.id)
          return 0;
       return this.parentid.CompareTo(obj.parentid);
    }
 }

Then a method in my Dao which brings back a list of all the Person Objects in the database under a group. Unfortunately it brings them back in the order they were entered and I want to sort them into a parent child relationship.

 i.e. (id,name,parent)
 person 1 (1,"Alice",null)
 person 2 (2,"Bob",1)
 person 3 (3,"Charlie",null)
 person 4 (4,"Dejgo",2) // child of a child
 person 5 (5,"Edgar", null)
 person 6 (6,"Florence", 3)

When I do a sort as mentioned in this answer like so:

 class PeopleBLL : IPeopleBLL {

    // spring properties
    private IGroupsBLL _groupsBLL;
    private IPeopleDao _peopleDao;

    // public properties where spring sets up the links to the other dao and bll methods
    public IGroupsBLL {
        set{_groupsBLL = value;}
    }
    public IPeopleDao {
        set{_peopleDao= value;}
    }

    // constructor
    public PeopleBLL()
    {
    }

    // method we are interested in
    public IList<Person> GetAllInGroup(int groupID){
       //get the group object
       Group groupObject = _groupsBLL.Get(groupID); 
       // get the people in the group
       IList<Person> people = _peopleDao.GetAllInGroup(groupObject);
       // do something here to sort the people
       people.Sort();
       // return the list of people to aspx page
       return people;
    }
 }

I get the list in the format

    person 2
 person 1
    person 6
 person 3
 person 5
       person 4

but I want it in the format

 person 1
    person 2
        person 4
 person 3
    person 6
 person 5

Any ideas?

EDIT:

I didn't put the rest of this in because I didn't want to confuse the question by all the extra technologies used but since I was asked. Spring.Net is used to link up all the objects, I have a 3-tier architecture Front end (asp.net pages), Business layer, and Dao layer which communicates with the database. I have updated the GetAllInGroup() to show everything it does but it is only the sorting I am interested in. The Dao uses an nHibernate Criteria Query to get all the objects under the group as follows.

  public IList<Person> getRegistrationsForDonor(Group groupObject) {
      IList<Person> rv = CurrentSession.CreateCriteria(typeof(Person),"p")
                         .CreateCriteria("Groups","g")
                         .Add(Expression.Eq("g.id", groupObject.id))
                         .List<Person>();
      return rv;
  }

And all this is started in an aspx page in an object datasource

 <asp:ObjectDataSource ID="ObjectDataSource1" OnObjectCreating="DataSource_ObjectCreating" runat="server" DataObjectTypeName="Domain.Person"  TypeName="BusinessLayer.PersonBLL" DeleteMethod="delete" SelectMethod="GetAllInGroup" UpdateMethod="update">
    <SelectParameters>
        <asp:ControlParameter ControlID="groupid" Type="Int32" DefaultValue="0" Name="groupID" />
    </SelectParameters>
</asp:ObjectDataSource>

which is then used by a gridview, so unfortunately I need to return a sorted IList from the BLL to the front end in the BLL method as a single IList.

Edit 2

Sorry An assumption has been made that all parents will be null at the top level and I can understand where that assumption can come from but you could in theory have only children in the group so none of the object would have a parent of null. for example the following is a valid group.

 person 2 (2,"Bob",1)
 person 4 (4,"Dejgo",2) // child of a child
 person 6 (6,"Florence", 3)

which I would hope would return

 person 2
    person 4
 person 6

Edit 3

Unfortunately all of this could not be surmised in a comment so I will have to respond here.

Having tried @jon-senchyna answer at 17:30, I am definitely getting closer, I have just tried this on some live data and it is nearly there but I seem to be running into problems with the children coming before the parents in the group. Take the following example in Group 1 there are 48 people, I will highlight the ones of interest.

 (789, "person 1", null)
 (822, "Person 34", null)
 (825, "Person 37", 789)
 (3060, "Person 47", 822)
 (3061, "Person 48", 825)

This is the order they are returned from the database, but when they are put through the sort they are in the order

 Person 48 - id: 3061
 Person 37 - id: 825
 Person 1 - id: 789
 Person 47 - id: 3060
 Person 34 - id: 822

So the children are next to the parents but in the reverse order, where I would like them in the order

 Person 1 - id: 789
 Person 37 - id: 825
 Person 48 - id: 3061
 Person 34 - id: 822
 Person 47 - id: 3060
Community
  • 1
  • 1
kamui
  • 3,339
  • 3
  • 26
  • 44
  • Can you please post rest of the code as well to get the clear idea what you doing in the code. – MMK Jun 12 '12 at 12:46
  • Off hand, I would make sure the class was mapped to identify the parent child relationship. Then I would do something like 'var q = from p in session.Query where p.parent == null order by p.name'. Then let NHibernate do the rest. Might not be a bad idea to override the CompareTo to use name or something else. – cloggins Jun 12 '12 at 13:00
  • Just a random note, you're missing a semicolon after your declaration of `_parent`, and your `_children` list should be of type `Person`, not `person`. I tried to make the edits, but SO complained that I didn't chaneg at least 6 characters... – Jon Senchyna Jun 12 '12 at 13:01
  • yep @jon-senchyna sorry that's just a typo, the code wouldn't compile if it was like that in the real code. Good spot though. – kamui Jun 12 '12 at 13:26
  • @kamui the accepted answer does not properly handled nested parents. See my comments on it below. – Jon Senchyna Jun 12 '12 at 14:53
  • @kamui: it also does not handle ids with more than 1 digit. For example, try three persons, one with `id=1`, one with `id=2`, and one with `id=13`. They will be in the wrong order. – Jon Senchyna Jun 12 '12 at 15:35
  • @JonSenchyna everything is going to be okay... it's just a webpage in a Q/A format, not a bomb – Marc Jun 12 '12 at 16:17
  • @You_Have_No_Idea True. I just want to make sure that his question gets an answer that works, be it your current one with some updates, mine, or someone else's. – Jon Senchyna Jun 12 '12 at 16:25

2 Answers2

2

If you want to do this via CompareTo, you may need some more comlpex logic to handle comparing the parent trees. Below is a possible solution:

public virtual Int32 CompareTo(Person person2)
{
    Stack<Person> parents1 = GetParents(this);
    Stack<Person> parents2 = GetParents(person2);
    foreach (Person parent1 in parents1)
    {
        Person parent2 = parents2.Pop();
        // These two persons have the same parent tree:
        // Compare their ids
        if (parent1 == null && parent2 == null)
        {
            return 0;
        }
        // 'this' person's parent tree is contained in person2's:
        // 'this' must be a parent of person2
        else if (parent1 == null)
        {
            return -1;
        }
        // 'this' person's parent tree contains person2's:
        // 'this' must be a child of person2
        else if (parent2 == null)
        {
            return 1;
        }
        // So far, both parent trees are the same size
        // Compare the ids of each parent.
        // If they are the same, go down another level.
        else
        {
            int comparison = parent1.id.CompareTo(parent2.id);
            if (comparison != 0)
            {
                return comparison;
            }
        }
    }
    // We should never get here anymore, but if we do,
    // these are the same Person
    return 0;
}

public static Stack<Person> GetParents(Person p)
{
    Stack<Person> parents = new Stack<Person>();
    // Add a null so that we don't have to check
    // if the stack is empty before popping.
    parents.Push(null);
    Person parent = p;
    // Recurse through tree to root
    while (parent != null)
    {
        parents.Push(parent);
        parent = parent.Parent;
    }
    return parents;
}

Update: Both parent-stacks now contain the original person, making the comparison logic a little easier.

Jon Senchyna
  • 7,867
  • 2
  • 26
  • 46
  • Great they are now in the correct order for the first level, but person 4 (4,"Dejgo",2) child of a child is still the last entry, do you have any ideas what needs to be added to accommodate multi-level structure. – kamui Jun 12 '12 at 14:00
  • I updated my answer to handle multiple levels of nesting properly. Unfortunately, it's much mroe complex now, but the extra functions like GetRoot and GetParents may be handy for other things aside from sorting. – Jon Senchyna Jun 12 '12 at 14:45
  • After redoing the code a little more, I was able to remove some of the complexity. A lot of the lines are just comments. Overall, you need to compare the parent trees of both Person objects, and then compare the Person objects themselves if they have the same parent trees. – Jon Senchyna Jun 12 '12 at 16:26
  • could you please look at Edit 3, you seem to be very close with your answer. – kamui Jun 12 '12 at 17:57
  • The only different between Edit 3 and the current version should be some comments and the fact that I removed unused functions (there were a couple leftover after I refactored). What is notworking properly with this answer? – Jon Senchyna Jun 12 '12 at 18:14
  • Sorry I meant the question I have a section marked **Edit 3** at the bottom with the problem, it seems that the sorting function you have offered is *so close*, it gets the children next to the parents just they are above not below in the list. – kamui Jun 12 '12 at 18:18
  • @kamui I just fixed a bug that was doing that. Try it out now. – Jon Senchyna Jun 12 '12 at 18:28
  • w00t, w00t we have a winner! Thank you this final edit solved all the problems. You are a gentlemen and a scholar good sir. – kamui Jun 12 '12 at 18:37
0

Just a consideration: You don't need to load all people. If you only load the people without a parent you will retrieve 1, 3, and 5. And you can access 2, 4 and 6 with the Children-property. To get a list of all 6 in that order (without a hierachy) you could go through the 3 root nodes recursivly and create the final list.

Franky
  • 651
  • 3
  • 11
  • Or to avoid round trips, load them all then from the database then do what you said but on a linq query which only returns entities with no parent, sorted by name – Kevin O'Donovan Jun 12 '12 at 12:58
  • Please see my edit, as they are [Person] in a [Group] that are returned, there is no guarantee that the root elements would have a parent of null. – kamui Jun 12 '12 at 13:52
  • Okay, that renders my consideration moot. – Franky Jun 12 '12 at 14:17