Can I programmatically find a Sitecore item's parent matching a certain template id?
Asked Answered
M

2

0

I have a custom Sitecore command that I would like to expand upon to determine if the current item has a parent item matching a certain template id.

When the command/button is clicked, I will have access to the current Item.

My initial attempt, since I had issues with escaping the FullPath for use in a query, was to loop through the parent item (and then that item's parent, etcetera) to determine if any matched the template.

However, there seems like there should be a better way to do this.

So given an item, what's the best way to find whether it has a parent item that is of a certain template?

Merely answered 3/5, 2016 at 22:35 Comment(1)
Per a meta question, I am asking this more focused variant of this question.Merely
O
1

SXA way

 public static Item GetParentOfTemplate(this Item item, ID templateId)
    {
        if (item == null)
        {
            return null;
        }
        ItemRelationCacheValue itemRelationCacheValue = ParentOfTemplateCache.GetCacheForDatabase(item.Database.Name)?.Get(item, templateId);
        if (itemRelationCacheValue != null)
        {
            return item.Database.GetItem(itemRelationCacheValue.OutputID);
        }
        Template template = TemplateManager.GetTemplate(templateId, item.Database);
        if (template == null)
        {
            return null;
        }
        ID iD = item.ID;
        while (item != null)
        {
            Template template2 = TemplateManager.GetTemplate(item);
            if (template2 != null && template2.DescendsFromOrEquals(template.ID))
            {
                ItemRelationCacheValue value = new ItemRelationCacheValue(iD, item.Database.Name, templateId, item.ID);
                ParentOfTemplateCache.GetCacheForDatabase(item.Database.Name)?.Set(value);
                return item;
            }
            item = item.Parent;
        }
        return null;
    }
Obscurantism answered 3/3, 2020 at 14:10 Comment(0)
T
0

There's not really a good way to do it. In a regular system you'd do it in SQL, but Sitecore has obfuscated their 'Nexus' code which I believe is where all of this data access/mapping logic happens thus I don't really know how that part of Sitecore works so it's a difficult to try to reverse engineer a query to do this efficiently.

What I use in my projects I use a recursive function like this. I use a delegate to determine if the item is a match for what I'm looking for, and just recurse up the tree - dropping out to return null if it reaches the root node.

public delegate bool ItemSelectionCondition(Item sitecoreItem);
public static Item GetAncestor(Item sitecoreItem, ItemSelectionCondition selectionCondition)
{
    Item parent = sitecoreItem.Parent;
    if (selectionCondition(parent))
    {
        return parent;
    }
    else if (Sitecore.ItemIDs.RootID == parent.ID)
    {
        return null;
    }
    else
    {
         return GetAncestor(parent, selectionCondition);
    }
}

So to work with this you'd need to define a delgate something like:

public static bool IsTemplateX(Item item)
{
   return item.TemplateID == Constants.TemplateXId;
}

It's quite a simple solution that's flexible enough to work with any Ancestor or Parent type things that I've needed to do.

Be warned though it will take several seconds to run the first time if Sitecore hasn't cached all of the items it needs to look at, but subsequent times it will take a bearable amount of time.

EDIT: Since this answer has gotten a few downvotes I thought I'd explain why it's the best way to do this in Sitecore.

The reson why a recursive approach is better that iterative is two-fold. First, it's less code so it looks nicer - but if that's not enough the second reason is that if you're careful about how you've structured your code and are compiling your project you'll be able to take advantage of tail recursion.

The reason why using Item.Axes.Ancestors (Or Item.Axes.GetAncestors() in later versions of Sitecore) is bad is because it has five steps:

  1. Allocate a List of Items
  2. Iteratively add All Ancestors to that list
  3. Reverse the order of the List
  4. Convert the list to an array
  5. Use Linq to filter the array

This means that you're doing twice as much allocation as if you were to just recurse to the top of the content tree, but you're also ruling out the possibility of exiting the recursion at the first relevant item - and avoiding unecessary calls to the database.

I do have to conceed that the comment about using a Lucene Index however is right - that's the way that is potentially the fastest in almost all scenarios just because it can reduce your time finding the item down to a single Sitecore Query - or no Sitecore Query if you index the data about that ancestor that you need.

Thesis answered 4/5, 2016 at 3:47 Comment(6)
How about something like item.Axes.Ancestors.Where (ancestor => ancestor.Template I'd == "xxx")Ashleaashlee
"My initial attempt was to loop through the parent item (and then that item's parent, etcetera) to determine if any matched the template." What is difference from @James Skemp initial approach?Stockton
@MartinDavies, using the Axes.Ancestors, will this not cause performance issue if it has many ancestors?Slesvig
@HishaamNamooya whether you use the Axes or a recursive function, you have the same number of ancestors. I would use the query, as long as its not a "fast" query, it will benefit from Sitecore's caching layers. Now if it was going the other way, a descendants call, then you are going to have performance issues. You would have to have a very deep tree for ancestors to really start causing a problem.Mean
@RichardSeal, yeah the axes.descendants causes performance issue. We had experienced this when developing our projectSlesvig
tbh - I can't remember the last time I used a Sitecore query. I almost always use the index to find items now. It is a much faster method :)Mean

© 2022 - 2024 — McMap. All rights reserved.