You have query correctly, i.e. specifying type and columns will improve the query performance and you should use caching with dependencies:
var menuList = CacheHelper.Cache(cs =>
{
var menu = new List<CMS.DocumentEngine.TreeNode>();
var menuPath = "/MenuTest/";
menu = DocumentHelper.GetDocuments("CMS.MenuItem")
.Columns("DocumentName", "NodeLevel", "DocumentCulture", "NodeID", "NodeParentID", "DocumentURLPath", "NodeAliasPath")
.Path(menuPath, PathTypeEnum.Children)
.Culture(CurrentDocument.DocumentCulture)
.OnSite(SiteContext.CurrentSiteName)
.OrderBy("NodeOrder", "NodeLevel")
.ToList();
if (cs.Cached)
{
// set dependency based on sub tree - node|<site name>|<alias path>|childnodes
var dpendencyConfig = new List<string>()
{
$"node|{SiteContext.CurrentSiteName}|/{menuPath}|childnodes".ToLower()
};
cs.CacheDependency = CacheHelper.GetCacheDependency(dpendencyConfig);
}
return menu;
},
new CacheSettings(60, "myMenuItems_" + SiteContext.CurrentSiteName + CurrentDocument.DocumentCulture));
Now you can render it the way you want:
var itemsByParentId = menuList.ToLookup(x => x.NodeParentID);
var firstLevel = menuList.Min(x => x.NodeLevel);
var level1Nodes = menuList.Where(x => x.NodeLevel == firstLevel);
var sbMenu = new StringBuilder();
sbMenu.Append("<ul>");
foreach (var menuItem in level1Nodes)
{
sbMenu.Append($"<li><a href='{menuItem.RelativeURL}'>{menuItem.DocumentName}</li>");
sbMenu.Append("<ul>");
foreach (var subMenuItem in itemsByParentId[menuItem.NodeID])
{
sbMenu.Append($"<li><a href='{subMenuItem.RelativeURL}'>{subMenuItem.DocumentName}</li>");
}
sbMenu.Append("</ul>");
}
sbMenu.Append("</ul>");
var result = sbMenu.ToString();
The code above is just to give you an idea how to approach. Adjust it to your needs.