Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Michaël Vanbrabandt 863 posts 3348 karma points c-trib
    Mar 23, 2017 @ 07:39
    Michaël Vanbrabandt
    0

    Which solution do you prefer to take a single node from the xml cache?

    Hi all,

    like in many projects we and you are building, we sometimes need to get for example the contact node on another page. In this way we can add a contact button which will redirect the customer to the contact page.

    But you have many solutions to accomplish this and we would like to know which one fits best for solving this and has the best performance:

    Solution 1:

    var _contactNode = Umbraco.TypedContentAtRoot()
        .DescendantsOrSelf("contact")
        .FirstOrDefault();
    

    Using the UmbracoHelper, take the IPublishedContent root node in your content structure and search for a descendant which has a document type alias of contact.

    Solution 2:

    var _contactNode = Model.Content.Site().Children
        .Where(x => x.DocumentTypeAlias == "contact")
        .FirstOrDefault();
    

    Take the typed content of the currentpage which then wil take its root node by calling the .Site() function and then search for a child node of document type contact.

    Solution 3:

    var _contactNode = Umbraco
        .TypedContentSingleAtXPath("//home/contact");
    

    Using the UmbracoHelper search for a node of type contact using a xpath.

    Other suggestions?

    /Michaël

  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Mar 23, 2017 @ 08:12
    Dave Woestenborghs
    100

    Michaël,

    The best solution here is the XPath. That is always the fastest option.

    An alternative to solution in 1 to avoid the descendantsorselft call would be :

    Model.Content.AncestorOrSelf("home").Children.Where(x => x.DocumentTypeAlias == "contact").FirstOrDefault()
    

    Have you read this page : https://our.umbraco.org/documentation/Reference/Common-Pitfalls/

    There are somethings explained what to avoid in querying.

    A lot also depends on how many content you have. Solution one will do fine on a small site. But when you have 20000 content items several levels deep, it will be slow.

    Dave

  • Michaël Vanbrabandt 863 posts 3348 karma points c-trib
    Mar 23, 2017 @ 08:20
    Michaël Vanbrabandt
    0

    Dave,

    thanks for your input here! Normally we only have small websites that won't exceed more then 500 nodes.

    Will read the common pitfalls which we haven't read before, looks very interesting.

    /Michaël

  • Michaël Vanbrabandt 863 posts 3348 karma points c-trib
    Mar 23, 2017 @ 09:33
    Michaël Vanbrabandt
    0

    @Dave,

    one more question or opinion, when using ModelsBuilder do you convert your result to the correct class like for example:

    var trainingsItems = Model.Content
        .Children.Where(
             t => t.IsVisible()
        ).OrderBy("name")
        .Select(t => new PageTrainingsItem(t));
    

    So that you can use the properties of that node instead of using .GetPropertyValue()? Or will this have an impact on the performance?

    /Michaël

  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Mar 23, 2017 @ 09:36
    Dave Woestenborghs
    1

    Hi Michaël,

    You can do this

    var trainingsItems = Model.Content
        .Children.OfType<PageTrainingsItem>.Where(
             t => t.IsVisible()
        ).OrderBy(x => x.Name);
    

    That would return them in the correct type. And also filter if you have differrent types as children

    Dave

  • Michaël Vanbrabandt 863 posts 3348 karma points c-trib
    Mar 23, 2017 @ 09:42
    Michaël Vanbrabandt
    0

    Dave,

    something new learned today! Didn't know about the .OfType<> possibility!

    /Michaël

  • Sotiris Filippidis 286 posts 1501 karma points
    Mar 23, 2017 @ 10:21
    Sotiris Filippidis
    0

    I think this is also valid

     var trainingsItems = Model.Content
        .Children<PageTrainingsItem>().Where(
             t => t.IsVisible()
        ).OrderBy(x => x.Name);
    
  • Sotiris Filippidis 286 posts 1501 karma points
    Mar 23, 2017 @ 08:31
    Sotiris Filippidis
    0

    I agree with Dave. XPath is the fastest option.

    That said, I often have the need to query for specific nodes throughout the site (the homepage, a special "configuration" node or just some content "roots" like a "products" or "news" node under which respective content is stored). So caching those specific nodes works best for me.

    I'm using strongly-typed models more often than not, so caching goes like this:

        public static PageNewsRoot GetNewsRootNode(IPublishedContent currPage)
        {
            return (PageNewsRoot)ApplicationContext.Current.ApplicationCache.RuntimeCache
               .GetCacheItem("cachednodefor_news_" + currPage.GetCulture().LCID.ToString(),
                   () => currPage.AncestorOrSelf(1).Descendant<PageNewsRoot>());
        }
    

    This caches a "PageNewsRoot" node in the runtime cache, for a specific language (in case the site is multilingual). It just requires any page to be passed as a parameter. I'm not using XPath (although I should!)

    This method goes under a static class called "CachedNodes", so a call to this method goes, for example, like this (very simplified):

    IEnumerable<NewsItem> news = CachedNodes.GetNewsRootNode(model).Children<NewsItem>();
    

    The only issue with this approach is that I have to clear the cache in case contents change. I'm using a brutal approach, just clearing the runtime cache each time a document is published or deleted, although it could be more fine-grained (only if documents of specific doctypes are altered). It goes like this:

    public class SiteEvents : ApplicationEventHandler
    {
        protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            base.ApplicationStarted(umbracoApplication, applicationContext);
            ContentService.Published += ContentService_Published;
            ContentService.Deleted += ContentService_Deleted;
        }
    
        private void ContentService_Deleted(IContentService sender, DeleteEventArgs<IContent> e)
        {
            ClearRuntimeCache(); 
        }
        private void ContentService_Published(Umbraco.Core.Publishing.IPublishingStrategy sender, PublishEventArgs<IContent> e)
        {
            ClearRuntimeCache();
        }
    
        private void ClearRuntimeCache()
        {
            //Clear all cache for keys starting with "cachenodefor_"                       
    
            ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch("cachednodefor_");
    
            //Any other action you need
        }
    }
    

    I know this might be totally out of the scope of the question, but I just wanted to mention my approach in case somebody finds it useful (or faulty, in which case please do comment!)

  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Mar 23, 2017 @ 08:46
    Dave Woestenborghs
    3

    Hi Sotiris,

    I'm not a fan of caching nodes. This is already cached by Umbraco itself. But if you do it you should change you cache clearing methods.

    The methods you use of the content service only get executed on the server they are fired. So in a load balanced environment this will not work, because the cache will only be cleared on the server where the content is changed. All other servers will have the old version in cache.

    If you want to cache than you should hook in to the cacherefreshers. This will make sure that the cache is cleared on all servers.

     public class UmbracoStartup : ApplicationEventHandler
     {
        protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            // handle cache updated event, so we can clear cache on all servers
            PageCacheRefresher.CacheUpdated += this.PageCacheRefresherCacheUpdated;
        }
    
        private void PageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e)
        {            
            switch (e.MessageType)
            {
                case MessageType.RefreshById:
                case MessageType.RemoveById:
                    var contentId = (int)e.MessageObject;
                    // do cache clearing here
                    break;
                case MessageType.RefreshByInstance:
                case MessageType.RemoveByInstance:
                    var content = e.MessageObject as IContent;
                    // do cache clearing here
                    break;
                }
            }
     }
    

    Dave

  • Sotiris Filippidis 286 posts 1501 karma points
    Mar 23, 2017 @ 08:50
    Sotiris Filippidis
    0

    You are absolutely right. The example I gave was NOT suitable for load balanced environments, I should have mentioned that it was intended for single-server environments only. I only wanted to demonstrate the approach.

    Regarding your remark about nodes being cached by Umbraco, it is true, but having to query the node tree to get a node can be VERY time consuming as I've found out myself in sites with over 10k-20k nodes. Even navigating upwards to the home page can take time. So I prefer to explicitly cache frequently used nodes in such cases.

  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Mar 23, 2017 @ 09:02
    Dave Woestenborghs
    1

    Hi Sotiris,

    Been doing some testing latetly..with different query options on a large multisite environment. XPath look up is fast without caching the result.

    What i some times do is cache the ids of the node. That way you don't need to cache the content. The id is something that does not change when content is changed, so could be cached without clearing the cache after a publish

    Dave

  • Sotiris Filippidis 286 posts 1501 karma points
    Mar 23, 2017 @ 09:35
    Sotiris Filippidis
    0

    Caching the id is a very good idea indeed.

    Ah, XPath...I miss the XSLT days where everything would come back instantly, no matter how complex the XPath query or how deep in the content structure it was.

    Working with strongly-typed models is, of course, much faster (thanks to IntelliSense), but there are many pitfalls regarding performance.

  • Dave Woestenborghs 3504 posts 12133 karma points MVP 8x admin c-trib
    Mar 23, 2017 @ 09:38
    Dave Woestenborghs
    0

    Hi Sotiris,

    For really expensive queries you could always use a XPathNavigator to work on the xml directly.

    See here : https://our.umbraco.org/documentation/Reference/Common-Pitfalls/#xpathnodeiterator-for-when-you-need-direct-xml-support

    Dave

  • Nik 1593 posts 7151 karma points MVP 6x c-trib
    Mar 23, 2017 @ 09:04
    Nik
    0

    An alternative option, is that you have a content picker on the home node (Or a site settings node) for selecting the contact page (or other pages that are used regularly in things like footers, headers, etc).

    Then you can simply get the selected node from the home page and use Umbraco.TypedContent() to retrieve said node (as the picker stores the ID).

Please Sign in or register to post replies

Write your reply to:

Draft