> Not sure if recursive is the right word, but here's the situation:

        Hierarchical fetches, tree-fetches, nested set model queries etc. it
has a truckload of names ;)

> Using AdventureWorks, LINQPad, and the Employee table.
>
> An Employee has a ManagerID column. If the value is NULL then that Employee
> has no manager.
>
> What I want to do is select the top-level employee, his/her subordinates,
and
> for each of those their subordinates, and so on.

        It's the typical textbook example of how to store a tree-based
structure in a single table. It's also the most inefficient way to do it, but
you can't do a thing about that of course, as it's not your db.

        The main drawback is, as you've found out, that you can't retrieve a
hierarchy or part of the hierarchy in a single query, you need 1 query per
tree-level.

        In the case you run into this again and you DO control the db schema,
you can take two different approaches.
1) use a 'precalc' table for the hierarchy, which is kept up to date with a
trigger. This comes down to querying the precalc table together with the real
table and you get any part of the hierarchy in 1 query. I explain it here with
sample code:
http://www.llblgen.com/tinyforum/GotoMessage.aspx?MessageID=17746&ThreadID=320
8
2) use the nested set model described by Celko. Use this link:
http://groups.google.com/group/microsoft.public.sqlserver.programming/browse_f
rm/thread/a0971b548a1daa06/d94afd1d56858df4?q=tree

        Ok, back to your problem at hand, where you have to deal with a table
which contains a complete hierarchy.

> Var query = from mgr in Employees
>                                 Where mgr.ManagerID == null
>                                 Select new
>                                 {
>                                                 Title = mgr.Title,
>                                                 Subordinates = from person
in
> Employees
>
> Where person.ManagerID = mgr.EmployeeID
>
> Select new { Title = person.Title }
>                                 };
>
>
> This gets me one level deep.  But I'd like to see how it can be written to
go
> deeper until eventually I arrive at an Employee who no subordinate.
>
> Hope that makes sense and looking forward to any info.

        This approach will of course not scale. If you add a hierarchy level
to the tree, you have to adjust all your queries.

        So it's better to take a different approach. This uses an ~O(2n)
algorithm to construct the tree in-memory, from a flat list. ( O(2n) worse
case)

        First, simply fetch the employees in full:
var q = from e in ctx.Employees select e;

We'll now build the tree. Let's assume there are more top-level managers, so
we'll add any top level manager to a list:

HashSet<Employee> roots = new HashSet<Employee>();

We'll need an ID -> Employee lookup index, so we'll create a Dictionary on the
fly:

Dictionary<int, Employee> idToEmployee = new Dictionary<int, Employee>();

At this point, we're set to build the tree. We'll simply traverse the
employees we've fetched and build the tree along the way. We need a list of
employees we can't add to a manager yet. This is the reason the algorithm is
O(2n) and not O(n). With sorting it can be made more efficient.

List<Employee> orphanedEmployees = new List<Employee>();

// build the tree
BuildTree(q, idToEmployee, roots, orphanedEmployees);

Here is the routine which builds the tree

// traverse the employees, build the tree along the way.
public void BuildTree(IEnumerable<Employee> toTraverse,
        Dictionary<int, Employee> idToEmployee,
        HashSet<Employee> roots,
        List<Employee> orphanedEmployees)
{
        foreach(Employee e in toTraverse)
        {
                // always first add the employee to the lookup dictionary
                idToEmployee[e.EmployeeID] =  e;

                if(!e.ManagerID.HasValue)
                {
                        // has no manager, is root
                        roots.Add(e);
                        continue;
                }
                Employee manager = null;
                if(idToEmployee.TryGetValue(e.ManagerID.Value, out manager))
                {
                        // manager found
                        e.Manager = manager;
                }
                else
                {
                        // manager is not yet seen, add to orphaned list
                        orphanedEmployees.Add(e);
                }
        }
        // it could be the order in which the employees were stored in the
list
        // to traverse was suboptimal (i.e. managers were stored after the
employees)              // we therefore traverse the orphaned list once
        BuildTree(orphanedEmployees, idToEmployee, roots, new
List<Employee>());
}

        Not tested, written down from memory.

                Frans


------------------------------------------------------------------------
Lead developer of LLBLGen Pro, the productive O/R mapper for .NET
LLBLGen Pro website: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------

===================================
This list is hosted by DevelopMentorĀ®  http://www.develop.com

View archives and manage your subscription(s) at http://discuss.develop.com

Reply via email to