具有讽刺意味的是,这个问题最难的部分可能是从数据库中获取一组满足特定路径谓词的受限记录。除非您使用提供分层查询支持的数据库(如 Oracle 的 CONNECT BY),否则这可能会变得相当复杂。查看this SO question 了解这方面的一些方法。
但是,假设您已经设法将数据加载...您可以将数据加载到自定义对象集合、DataSet 或 XML 结构中(如果您不关心操作数据除了作为字符串)。如果您有自定义对象,则可以通过查询整个数据集以查找与父键匹配的记录来添加一个公开子集合的属性(参见下面的示例)。如果记录的数量很大,由于搜索集合的成本,这开始变得无效。这就是 DataSet 解决方案的亮点 - 因为您可以在 ParentID 列上添加自定义索引,这将提高查找的性能。您可以在此处read about Typed Data Sets 了解更多关于如何在这个方向上扩展我的示例。
这是一个支持树形导航的自定义对象示例。我使用的是目录结构(文件夹),因为它是分层数据的自然示例。同样,请注意在没有索引结构的情况下使用此代码,因为完整的子遍历将是 O(n2)。
class Folder // could be converted to a DataRow-derivative
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public IEnumerable<Folder> GetChildren( IEnumerable<Folder> allFolders )
{
// NOTE: becomes expensive on large hierarchies on unindexed data
// a slightly better version, could replace IEnumerable<Folder>
// with IOrderedEnumerable<Folder> which would allow the use of
// a binary search algorithm for finding children (assuming it's
// ordered on the ParentId property).
return AllFolders.Where( x => x.ParentId.HasValue &&
x.ParentId.Value = this.Id );
}
}
// elsewhere in your code...
void LoadAndPrintFolders()
{
// load the folder data... from database, or wherever
IEnumerable<Folder> allFolders = LoadFolders();
// find all root folders (let's say, those that have a null ParentId
IEnumerable<Folder> rootFolders =
allFolders.Where( x => !x.ParentId.HasValue );
// iterate over all of the data as a tree structure...
foreach( var folder in rootFolders )
{
PrintFolder( allFolders, folder, 0 );
}
}
// recursive function to print all folders...
void PrintFolder( IEnumerable<Folder> allFolders, Folder f, int level )
{
Console.WriteLine( "{0}{1}", new string('\t',level), f.Name );
foreach( var folder in f.GetChildren( allFolders )
{
PrintFolder( allFolders, folder, level + 1 );
}
}