Search code examples
c#asp.netumbracoumbraco7

Umbraco 7.3 - find all children of DocumentTypeAlias without having to dot your way through with LINQ


I have a pretty basic example:

string areaType = "PrivateHomes";

var plotProperties = Model.Content.AncestorOrSelf(1)
    .Children.Where(c => c.DocumentTypeAlias.Equals("Properties")).First()
    .Children.Where(c => c.Name == areaType).First()
    .Children.First()
    .Children.First();

foreach(var child in plotProperties.Children())
{ /* Do stuff */ }

This code seems... counterproductive... somehow... My goal is to get the children of DocumentType "Types" that has a name "PrivateHomes" - is there any way to achieve this without doing what I'm doing here? The code above is working, but I'm not in any capacity to assess the impact of it.


Solution

  • There are a lot of options, but here are some interesting ones to try in no particular order.

    Option 1: TypedContentAtXPath
    I really like this approach. It is my go-to in Umbraco 7. In Umbraco 7, the published content is cached as xml. You can explore the xml in the /App_Data/umbraco.config to get a feel for its shape. You can also query it with XPATH like this:

    var privateHomesNode = Umbraco.TypedContentSingleAtXPath("//Properties/*[@nodeName='PrivateHomes'");
    

    Option 2: XPathNavigator
    This option isn't as fun to write, but it runs very quickly. I like to use this option occasionally when my goal is to retrieve a value and not a whole model.

    var navigator = UmbracoContext.Current.ContentCache.GetXPathNavigator();
    var privateHomesExpression = navigator.Compile("root//Properties//*[@nodeName='PrivateHomes']");
    var firstPrivateHome = navigator.SelectSingleNode(privateHomesExpression);
    var firstPrivateHomeId = firstPrivateHome?.GetAttribute("id", "")
    

    Option 3: Linq-like Extensions on UmbracoHelper
    This is the option you were trying. There are a lot of different ways to do these. If you are using ModelsBuilder, you can use strongly typed models in your query as well. You could use .Descendants() but you have to be really careful with that. It can very quickly turn into a costly query. You could spend a lot of time constructing IPublishedContent models out of data you don't even care to retrieve.

    var privateHomesNode = Model.Content.AncestorOrSelf(1)
        .Children<CategoryPage>().First()
        .Children.First(c => c.Name == "PrivateHomes");
    

    Option 4: Examine Query
    There are a lot of ways to query examine. You can do a simple TypedSearch like Umbraco.TypedSearch("PrivateHomes"), but that might return results you don't intend. You can do something more complex like this:

    var externalSearcher = ExamineManager.Instance.SearchProviderCollection["externalSearcher"];
    var searchCriteria = _internalSearcher.CreateSearchCriteria();
    var query = searchCriteria.RawQuery("nodeName:PrivateHomes");
    var results = _internalSearcher.Search(query);
    

    The biggest thing you want to avoid is using .Descendants() when you can help it. We did some benchmarking in 2017 of different ways you could query content. It wasn't super thorough, but one of our findings was that queries using .Descendants() like the following can be extremely slow: Model.Content.AncestorOrSelf(1).DescendantsOrSelf().Where(c => c.IsDocumentType("myDocType")). When we ran the above query repeatedly, the results were better because Umbraco would end up caching the results in memory. We found that querying by XPath like Umbraco.TypedContentAtXPath("//myDocType") didn't experience the first initial slow load. I believe this is because the XPath query doesn't spend time constructing IPublishedContent models out of all of the intermediate results.