Używając linq2sql możesz zrobić:
List<PageInfo> GetHierarchicalPages()
{
var pages = myContext.PageInfos.ToList();
var parentPages = pages.Where(p=>p.ParentId == null).ToList();
foreach(var page in parentPages)
{
BuildTree(
page,
p=> p.Pages = pages.Where(child=>p.pageId == child.ParentId).ToList()
);
}
}
void BuildTree<T>(T parent, Func<T,List<T>> setAndGetChildrenFunc)
{
foreach(var child in setAndGetChildrenFunc(parent))
{
BuildTree(child, setAndGetChildrenFunc);
}
}
Zakładając, że zdefiniujesz właściwość Pages w PageInfo, taką jak:
public partial class PageInfo{
public List<PageInfo> Pages{get;set;}
}
Przetwarzanie w celu umieszczenia go w hierarchii odbywa się po stronie aplikacji internetowej, co pozwala uniknąć dodatkowego obciążenia serwera sql. Pamiętaj też, że ten rodzaj informacji jest idealnym kandydatem do buforowania.
Możesz zrobić render, jak wspomniał Rex. Alternatywnie można nieco rozszerzyć tę implementację i sprawić, by obsługiwała interfejsy hierarchii i używała kontrolek asp.net.
Aktualizacja 1: W przypadku odmiany renderowania, o którą prosiłeś w komentarzu, możesz:
var sb = new System.IO.StringWriter();
var writer = new HtmlTextWriter(sb);
// rex's rendering code
var html = sb.ToString();