I have been spending a bit of time looking at the Sitecore Debugger. Using this you can see the 'hotspots', the components on the page that take the longest time to load and make the most calls to the database.
One of these hotspots in our implementation is the primary navigation component, which as you can imagine has to read lots of items in order to determine what to include in the menu. This got me thinking, about how we might improve our implementation and also to wondering what the best practice approach was.
As a brief intro to our approach, we do the following:
- Settings node has MultiList field for "PrimaryNavigation" Items (these form top level)
- Each PrimaryNavigation Item also has a MultiList field for "Sections" (these form sections within a dropdown)
- Each section has a MultiList field for "MenuItems" (which is effectively the final link)
Much of the data retrieval utilizes a method called GetLinkedItem, which cycles through the various MultiList fields grabbing the items for the menu.
public static List<Item> GetLinkedItems(Database database, string fieldValue)
{
var items = new List<Item>();
if (!string.IsNullOrEmpty(fieldValue))
{
var ids = fieldValue.Split(new char[] { '|' });
foreach (var id in ids)
{
var linkedItem = database.GetItem(new Sitecore.Data.ID(ParseId(id)));
if (linkedItem != null && !linkedItem.Publishing.NeverPublish)
{
items.Add(linkedItem);
}
}
}
return items;
}
With this approach, the debugger is showing some 4500 item reads to populate a menu that contains approximately 150 items.
I think one of the reasons for the discrepancy is that when an item is loaded, it also reads its child items. Either way, this seems to me to be a bit inefficient.
My first thought was to try getting the data from the web index, with code similar to the one below:
public static List<ContentSearchModelBase> GetLinkedItemsFromIndex(Database database, string fieldValue)
{
var items = new List<ContentSearchModelBase>();
if (!string.IsNullOrEmpty(fieldValue))
{
var ids = fieldValue.Split(new char[] { '|' }).Select(x => new ID(x));
var index = database.Name == "master" ? "sitecore_master_index" : "sitecore_web_index";
using (var context = ContentSearchManager.GetIndex(index).CreateSearchContext())
{
var q = context.GetQueryable<ContentSearchModelBase>()
.Where(x => !x.NeverPublish && ids.Contains(x.ItemId)).GetResults();
var result = q.Hits.Select(h => h.Document).ToList();
if (result != null)
return result;
}
}
return items;
}
However, whilst this reduced the number of Item reads, it was drastically less efficient!
I appreciate in the grand scheme of things - when the component gets cached the overhead becomes less of an issue. But I was wondering if anyone can recommend a better more efficient approach to building a multi-layer navigation menu component?