> 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