<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by dada</title><link href="http://world.optimizely.com" /><updated>2025-09-29T12:10:42.0000000Z</updated><id>https://world.optimizely.com/blogs/dada/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Trigger DXP Warmup Locally to Catch Bugs &amp; Performance Issues Early</title><link href="https://world.optimizely.com/blogs/dada/dates/2025/9/catch-bugs-early-triggering-dxp-warmup-locally/" /><id>&lt;p&gt;Here&amp;rsquo;s our documentation on &lt;strong&gt;warmup in DXP&lt;/strong&gt;:&lt;br /&gt;&#128279; &lt;a href=&quot;https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites&quot;&gt;https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What I didn&amp;rsquo;t know is that you can also trigger&amp;nbsp;&lt;strong&gt;warmup in your local development environment&lt;/strong&gt;. &lt;br /&gt;This can help expose bugs or issues before deploying to DXP and INT/PREP/PROD.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;if (_webHostingEnvironment.IsDevelopment())
{

    services.AddCmsWarmup(configure =&amp;gt;
    {
        configure.Disable = false;
        configure.FollowLinkLevel = 10; // Should cover most pages?
        configure.WaitingForStartupTimeout = TimeSpan.FromSeconds(180);
        configure.StartupWaitingInterval = TimeSpan.FromMilliseconds(10);
        configure.RequestTimeout = TimeSpan.FromMinutes(1);
        configure.MaxNumberParallelRequests = 10; // Increase to speed up warmup and simulate more load
        configure.AdditionalPaths.Add(&quot;/en/alloy-plan&quot;); // Add path to warmup that crawling won&#39;t find following links                    
    });

...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;br /&gt;Note:&lt;/strong&gt; &lt;em&gt;.AddCmsWarmup()&lt;/em&gt;&lt;strong&gt; &lt;/strong&gt;is already implicitly triggered when using &lt;em&gt;.AddCloudPlatformSupport()&lt;/em&gt;. However, for reasons I won&amp;rsquo;t go into here, it&amp;rsquo;s not something you trigger in your local/development environment.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2025-09-29T12:10:42.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Make Global Assets Site- and Language-Aware at Indexing Time</title><link href="https://world.optimizely.com/blogs/dada/dates/2025/6/make-global-assets-site--and-language-aware-at-indexing-time/" /><id>&lt;p&gt;I had a support case the other day with a question around search on global assets on a multisite. This is the result of that investigation. &lt;br /&gt;This code is provided as-is but please let me know if you have any feedback.&lt;/p&gt;
&lt;p&gt;In Episerver (Optimizely) CMS, &lt;strong&gt;global assets&lt;/strong&gt;&amp;mdash;such as images, documents, and blocks stored in the Global Assets folder&amp;mdash;are not tied to a specific site or language.&amp;nbsp;&lt;br /&gt;Yet, the content that references typically is.&lt;/p&gt;
&lt;p&gt;You might have noticed a common issue:&lt;br /&gt;When implementing a search on &lt;em&gt;Site A&lt;/em&gt;, assets referenced &lt;strong&gt;only&lt;/strong&gt; by content on &lt;em&gt;Site B&lt;/em&gt; still appear in the results and vice versa. That&amp;rsquo;s maybe not what we want .&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Assets used &lt;strong&gt;exclusively&lt;/strong&gt; on &lt;em&gt;Site A&lt;/em&gt;, or&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Assets &lt;strong&gt;shared&lt;/strong&gt; between &lt;em&gt;Site A&lt;/em&gt; and &lt;em&gt;Site B&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When a user searches on &lt;em&gt;Site A&lt;/em&gt;, you want results that are relevant &lt;strong&gt;only&lt;/strong&gt; to that site&amp;mdash;including global assets referenced by that site&amp;rsquo;s content. Likewise, if an asset is shared between both sites, it should appear in both result sets.&lt;/p&gt;
&lt;p&gt;To support this, your &lt;strong&gt;indexing process&lt;/strong&gt; must make global assets &lt;strong&gt;aware of which sites and languages&lt;/strong&gt; reference them.&lt;/p&gt;
&lt;h2&gt;This&amp;nbsp;Is the Way&lt;/h2&gt;
&lt;p&gt;If you&#39;re using &lt;strong&gt;UnifiedSearch&lt;/strong&gt;, this filtering on Site ID and language typically works out of the box via:&lt;/p&gt;
&lt;p&gt;* FilterOnCurrentSite() / FilterOnSite()&lt;br /&gt;* FilterForVisitor() / PublishedInLanguage()&lt;/p&gt;
&lt;p&gt;And for typed searches, you can easily apply these filters yourself.&lt;/p&gt;
&lt;h2&gt;Update the Initialization Module&lt;/h2&gt;
&lt;p&gt;To get this working for global assets, we override the default indexing behavior for SiteId() and PublishedInLanguage() by excluding the built-in fields and replacing them with our own logic.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;IContentMedia&amp;gt;().ExcludeField(x =&amp;gt; x.SiteId());
SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;IContentMedia&amp;gt;().IncludeField(x =&amp;gt; x.SiteId(true));
SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;IContentMedia&amp;gt;().ExcludeField(x =&amp;gt; x.PublishedInLanguage());
SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;IContentMedia&amp;gt;().IncludeField(x =&amp;gt; x.PublishedInLanguage(true));&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Extension Methods&lt;/h2&gt;
&lt;p&gt;We define custom logic in a static class to determine which &lt;strong&gt;sites&lt;/strong&gt; and &lt;strong&gt;languages&lt;/strong&gt; reference a given global asset.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Find.Cms;
using EPiServer.Framework.Cache;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using EPiServer.Find.Helpers;

public static class GlobalAssetsExtensions
{
    private static readonly Lazy&amp;lt;ISiteDefinitionResolver&amp;gt; _siteDefinitionResolver =
        new(() =&amp;gt; ServiceLocator.Current.GetInstance&amp;lt;ISiteDefinitionResolver&amp;gt;());

    private static readonly Lazy&amp;lt;IContentLoader&amp;gt; _contentLoader =
        new(() =&amp;gt; ServiceLocator.Current.GetInstance&amp;lt;IContentLoader&amp;gt;());

    private static readonly Lazy&amp;lt;IContentRepository&amp;gt; _contentRepository =
        new(() =&amp;gt; ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;());

    private static readonly Lazy&amp;lt;IContentVersionRepository&amp;gt; _versionContentRepository =
        new(() =&amp;gt; ServiceLocator.Current.GetInstance&amp;lt;IContentVersionRepository&amp;gt;());

    private static readonly Lazy&amp;lt;IObjectInstanceCache&amp;gt; _objectCache =
        new(() =&amp;gt; ServiceLocator.Current.GetInstance&amp;lt;IObjectInstanceCache&amp;gt;());

    private static readonly Lazy&amp;lt;IContentCacheKeyCreator&amp;gt; _contentCacheKeyCreator =
        new(() =&amp;gt; ServiceLocator.Current.GetInstance&amp;lt;IContentCacheKeyCreator&amp;gt;());

    public static IEnumerable&amp;lt;string&amp;gt; SiteId(this IContentMedia content, bool foobar)
    {        
        content.ValidateNotNullArgument(nameof(content));

        var siteDefinitionResolver = _siteDefinitionResolver.Value;
        var contentLoader = _contentLoader.Value;
        var contentRepository = _contentRepository.Value;
        var objectCache = _objectCache.Value;
        var contentCacheKeyCreator = _contentCacheKeyCreator.Value;

        if (siteDefinitionResolver.GetByContent(content.ContentLink, false) == null)
        {
            if (ContentReference.IsNullOrEmpty(content.ParentLink) ||
                !contentLoader.TryGet&amp;lt;IContent&amp;gt;(content.ParentLink, out _))
            {
                return Enumerable.Empty&amp;lt;string&amp;gt;();
            }

            if (contentLoader.GetAncestors(content.ContentLink)
                             .Any(x =&amp;gt; x.ContentLink == ContentReference.GlobalBlockFolder))
            {
                var cacheKey = $&quot;SiteIds:{content.ContentLink.ID}&quot;;
                var cachedSiteIds = objectCache.Get(cacheKey) as IEnumerable&amp;lt;string&amp;gt;;
                if (cachedSiteIds != null)
                {
                    return cachedSiteIds;
                }

                var contentRefs = new HashSet&amp;lt;ContentReference&amp;gt;();

                foreach (var link in contentRepository.GetReferencesToContent(content.ContentLink, false))
                {
                    if (!ContentReference.IsNullOrEmpty(link.OwnerID))
                    {
                        contentRefs.Add(link.OwnerID);
                    }
                }

                var siteIds = new HashSet&amp;lt;string&amp;gt;();
                foreach (var contentRef in contentRefs)
                {
                    var site = siteDefinitionResolver.GetByContent(contentRef, false);
                    if (site != null)
                    {
                        siteIds.Add(site.Id.ToString());
                    }
                }

                objectCache.Insert(cacheKey, siteIds, new CacheEvictionPolicy(
                    TimeSpan.FromMinutes(120), CacheTimeoutType.Sliding,
                    [contentCacheKeyCreator.VersionKey]));

                return siteIds;
            }
        }

        return [content.SiteId()];
    }

    public static Dictionary&amp;lt;string, LanguagePublicationStatus&amp;gt; PublishedInLanguage(this IContentMedia content, bool foobar)
    {
        content.ValidateNotNullArgument(nameof(content));

        if (ContentReference.IsNullOrEmpty(content?.ContentLink))
        {
            return null;
        }

        var contentLoader = _contentLoader.Value;
        var contentRepository = _contentRepository.Value;
        var versionRepository = _versionContentRepository.Value;
        var objectCache = _objectCache.Value;
        var contentCacheKeyCreator = _contentCacheKeyCreator.Value;

        if (contentLoader.GetAncestors(content.ContentLink)
                         .Any(x =&amp;gt; x.ContentLink == ContentReference.GlobalBlockFolder))
        {
            // Should we need to check whether entire ancestor chain are resolvable?

            var cacheKey = $&quot;Languages:{content.ContentLink.ID}&quot;;
            var cachedLanguages = objectCache.Get(cacheKey) as Dictionary&amp;lt;string, LanguagePublicationStatus&amp;gt;;
            if (cachedLanguages != null)
            {
                return cachedLanguages;
            }

            var contentRefs = new HashSet&amp;lt;ContentReference&amp;gt;();

            foreach (var link in contentRepository.GetReferencesToContent(content.ContentLink, false))
            {
                if (!ContentReference.IsNullOrEmpty(link.OwnerID))
                {
                    contentRefs.Add(link.OwnerID);
                }
            }

            var languages = new Dictionary&amp;lt;string, LanguagePublicationStatus&amp;gt;(StringComparer.OrdinalIgnoreCase);

            foreach (var contentRef in contentRefs)
            {
                var publishedLanguages = versionRepository
                    .ListPublished(contentRef)
                    .Select(x =&amp;gt; x.LanguageBranch);
                
                foreach (var lang in publishedLanguages)
                {
                    if (contentLoader.TryGet&amp;lt;IContent&amp;gt;(contentRef, new LanguageSelector(lang), out var contentInLang))
                    {
                        var langStatus = contentInLang.PublishedInLanguage();
                        if (langStatus != null)
                        {
                            foreach (var kvp in langStatus)
                            {
                                languages.TryAdd(kvp.Key, kvp.Value);
                            }
                        }
                    }
                }
            }

            objectCache.Insert(cacheKey, languages, new CacheEvictionPolicy(
                TimeSpan.FromMinutes(120), CacheTimeoutType.Sliding,
                [contentCacheKeyCreator.VersionKey]));

            return languages;
        }

        return content.PublishedInLanguage();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What It Looks Like in the Index&lt;/h2&gt;
&lt;p&gt;After reindexing a global asset referenced across different sites and languages, it will now include enriched metadata, like this&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;&quot;PublishedInLanguage&quot;: { &quot;sv&quot;: { &quot;StartPublish$$date&quot;: &quot;2017-02-22T11:24:00Z&quot; },
 &quot;fi&quot;: { &quot;StartPublish$$date&quot;: &quot;2025-06-18T11:03:00Z&quot; },
 &quot;en&quot;: { &quot;StartPublish$$date&quot;: &quot;2012-10-04T11:53:00Z&quot; },
 &quot;en-GB&quot;: { &quot;StartPublish$$date&quot;: &quot;2025-05-21T06:24:00Z&quot; } }, 
&quot;SiteId&quot;: [ &quot;site-a-guid&quot;, &quot;site-b-guid&quot; ]&lt;/code&gt;&lt;/pre&gt;</id><updated>2025-06-26T16:11:48.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Create a multi-site aware custom search provider using Search &amp; Navigation</title><link href="https://world.optimizely.com/blogs/dada/dates/2025/6/create-a-multi-site-aware-custom-search-provider-using-search--navigation/" /><id>&lt;p&gt;In a multisite setup using Optimizely CMS, searching for pages can be confusing. The default CMS search regardless of search provider does not clearly indicate which site a given result belongs to&amp;mdash;especially when you have identical page names across multiple sites.&lt;/p&gt;
&lt;p&gt;This post shows how you can create a custom search provider, using &lt;strong&gt;Search &amp;amp; Navigation&lt;/strong&gt;, to make your search results site-aware.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;By creating a custom search provider that appends site context (like the start page or site name) to each result, you can make the search output clearer and easier to work with.&lt;/p&gt;
&lt;p&gt;In the example below, we add the &lt;strong&gt;start page name&lt;/strong&gt; as a suffix to the page title in the results. This makes it obvious which site a page belongs to. Pages that are &lt;strong&gt;not under any site start page&lt;/strong&gt; appear without a suffix and show up &lt;strong&gt;at the top of the results&lt;/strong&gt;, since they are not associated with any specific site.&amp;nbsp;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can easily adapt this to use the &lt;strong&gt;site name&lt;/strong&gt; instead of the start page name if you prefer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This code is provided as-is, without guarantees or support. However, I&amp;rsquo;d be happy to hear your feedback or thoughts in the comments.&lt;/p&gt;
&lt;h2&gt;Implement it&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add new class MultiSitePageSearchProvider.cs&amp;nbsp;to your solution. Attached further down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In &lt;em&gt;Admin mode &amp;gt; Settings -&amp;gt; Search Configuration&lt;/em&gt;, enable the new &lt;strong&gt;Find pages (Multisite) &lt;/strong&gt;provider and disable the default &lt;strong&gt;Find pages&lt;/strong&gt; provider.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Try a few searches in edit mode and confirm that results now show which site they belong to.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Example results before&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/link/ca9ad5c76b334506ba3cf5c898defcd3.aspx&quot; alt=&quot;&quot; width=&quot;359&quot; height=&quot;346&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;... and after&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/link/d10534120a684ba8b3fbe10102758411.aspx&quot; alt=&quot;&quot; width=&quot;375&quot; height=&quot;367&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;MultiSitePageSearchProvider.cs&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;using EPiServer.Find;
using EPiServer.Find.Framework;
using EPiServer.Find.UI.Models;
using EPiServer.Find.UnifiedSearch;
using EPiServer.Framework;
using EPiServer.Framework.Localization;
using EPiServer.Globalization;
using EPiServer.ServiceLocation;
using EPiServer.Shell;
using EPiServer.Shell.Search;
using EPiServer.Web;
using EPiServer.Web.Routing;
using Newtonsoft.Json.Linq;


namespace EPiServer.Find.Cms.SearchProviders
{

    [SearchProvider]
    public class MultiSitePageSearchProvider : EnterprisePageSearchProvider
    {

        private readonly UIDescriptorRegistry _uiDescriptorRegistry;        
        private readonly ISiteDefinitionRepository _siteDefinitionRepository;
        private readonly IContentLoader _contentLoader;
        private readonly IClient _searchClient;
        private readonly ILogger&amp;lt;MultiSitePageSearchProvider&amp;gt; _logger;

        private const string AllowedTypes = &quot;allowedTypes&quot;;
        private const string RestrictedTypes = &quot;restrictedTypes&quot;;

        public override string Area =&amp;gt; FindContentSearchProviderConstants.PageArea;
        public override string Category =&amp;gt; &quot;Find pages (Multisite)&quot;;

        public MultiSitePageSearchProvider(
            LocalizationService localizationService,
            ISiteDefinitionResolver siteDefinitionResolver,
            IContentTypeRepository&amp;lt;PageType&amp;gt; contentTypeRepository,
            UIDescriptorRegistry uiDescriptorRegistry,
            EditUrlResolver editUrlResolver,
            ServiceAccessor&amp;lt;SiteDefinition&amp;gt; siteDefinitionAccessor,
            IContentLanguageAccessor contentLanguageAccessor,
            IUrlResolver urlResolver,
            ITemplateResolver templateResolver,
            IContentRepository contentRepository,
            ISiteDefinitionRepository siteDefinitionRepository,
            IContentLoader contentLoader,
            IClient searchClient,
            ILogger&amp;lt;MultiSitePageSearchProvider&amp;gt; logger)
            : base(localizationService, siteDefinitionResolver, contentTypeRepository, uiDescriptorRegistry, editUrlResolver, siteDefinitionAccessor, contentLanguageAccessor, urlResolver, templateResolver, contentRepository)
        {
            _uiDescriptorRegistry = uiDescriptorRegistry;
            _siteDefinitionRepository = siteDefinitionRepository;
            _contentLoader = contentLoader;
            _searchClient = searchClient;
            _logger = logger;
        }

        public override IEnumerable&amp;lt;SearchResult&amp;gt; Search(Query query)
        {
            Validator.ThrowIfNull(&quot;SearchProviderFactory.Instance.AccessFilter&quot;, FilterFactory.Instance.ContentAccessFilter);
            Validator.ThrowIfNull(&quot;SearchProviderFactory.Instance.CultureFilter&quot;, FilterFactory.Instance.CultureFilter);
            Validator.ThrowIfNull(&quot;SearchProviderFactory.Instance.RootsFilter&quot;, FilterFactory.Instance.RootsFilter);

            query.MaxResults = 20;

            ITypeSearch&amp;lt;IContentData&amp;gt; searchQuery = GetFieldQuery(query.SearchQuery, query.MaxResults)
                .Filter(x =&amp;gt; x.MatchTypeHierarchy(typeof(IContentData)));

            var allowedTypes = GetContentTypesFromQuery(AllowedTypes, query);
            var restrictedTypes = GetContentTypesFromQuery(RestrictedTypes, query);

            FilterContext filterContext = FilterContext.Create&amp;lt;IContentData, ContentType&amp;gt;(query);
            searchQuery = FilterFactory.Instance.AllowedTypesFilter(searchQuery, filterContext, allowedTypes);
            searchQuery = FilterFactory.Instance.RestrictedTypesFilter(searchQuery, filterContext, restrictedTypes);
            searchQuery = FilterFactory.Instance.ContentAccessFilter(searchQuery, filterContext);
            searchQuery = FilterFactory.Instance.CultureFilter(searchQuery, filterContext);
            searchQuery = FilterFactory.Instance.RootsFilter(searchQuery, filterContext);

            var contentLinksWithLanguage = Enumerable.Empty&amp;lt;ContentInLanguageReference&amp;gt;();

            try
            {
                contentLinksWithLanguage = searchQuery
                    .Select(x =&amp;gt;
                        new ContentInLanguageReference(
                            new ContentReference(((IContent)x).ContentLink.ID,
                                                 ((IContent)x).ContentLink.ProviderName),
                            ((ILocalizable)x).Language.Name))
                    .StaticallyCacheFor(TimeSpan.FromMinutes(1), UnifiedWeightsCache.ChangeToken)
                    .GetResultAsync()
                    .GetAwaiter()
                    .GetResult();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, &quot;Error during CMS page search query execution: {Message}&quot;, ex.Message);
                return Enumerable.Empty&amp;lt;SearchResult&amp;gt;();
            }

            PageData content = null;

            // Check if there are multiple sites with actual start pages
            var hasMultipleResolvableSites = _siteDefinitionRepository.List()
                                                                    .Where(site =&amp;gt; !ContentReference.IsNullOrEmpty(site.StartPage) &amp;amp;&amp;amp;
                                                                                   _contentLoader.TryGet&amp;lt;IContent&amp;gt;(site.StartPage, out _))
                                                                    .Skip(1)
                                                                    .Any();

            return contentLinksWithLanguage
                .Where(
                    searchResult =&amp;gt;
                    _contentLoader.TryGet&amp;lt;PageData&amp;gt;(searchResult.ContentLink,
                                                       !String.IsNullOrEmpty(searchResult.Language)
                                                           ? new LanguageSelector(searchResult.Language)
                                                           : LanguageSelector.AutoDetect(true), out content))
                .Select(item =&amp;gt;
                {
                    var result = CreateSearchResult(content);

                    if (hasMultipleResolvableSites &amp;amp;&amp;amp; Guid.TryParse(content.SiteId(), out var siteId))
                    {                        
                        var site = _siteDefinitionRepository.List().FirstOrDefault(s =&amp;gt; s.Id == siteId);
                        if (site != null)
                        {
                            var startPageName = _contentLoader.Get&amp;lt;IContent&amp;gt;(site.StartPage)?.Name;

                            if (!string.IsNullOrEmpty(startPageName))
                            {
                                result.Metadata.Add(&quot;SortKey&quot;, startPageName);
                                result.Title = $&quot;{startPageName} \\ {result.Title}&quot;; // Suffix the page title with the startPage name
                            }                        
                        }
                    }

                    return result;

                })
                .OrderBy(x =&amp;gt; x.Metadata.TryGetValue(&quot;SortKey&quot;, out var sortKey) ? sortKey : string.Empty);

        }


        private new ITypeSearch&amp;lt;IContentData&amp;gt; GetFieldQuery(string SearchQuery, int maxResults)
        {

            var language = ResolveSupportedLanguageBasedOnPreferredCulture();

            if (String.IsNullOrEmpty(SearchQuery))
            {
                SearchQuery = &quot;*&quot;;
            }

            var query = _searchClient.Search&amp;lt;IContentData&amp;gt;(language)
                .For(SearchQuery);

            if (StringExtensions.IsAbsoluteUrl(SearchQuery))
            {
                query = query.InField(x =&amp;gt; ((ISearchContent)x).SearchHitUrl);
                return query
                .Take(maxResults);
            }

            query = query.InField(x =&amp;gt; ((IContent)x).Name, 10)
                         .InField(x =&amp;gt; ((IContent)x).ContentTypeName(), 0.5)
                         .InField(x =&amp;gt; x.SearchText());

            int parsedQuery;
            if (int.TryParse(SearchQuery, out parsedQuery))
            {
                query = query.InField(x =&amp;gt; ((IContent)x).ContentLink.ID, 10);
            }

            DateTime parsedDate;
            if (DateTime.TryParse(SearchQuery, out parsedDate))
            {
                query = query.InField(x =&amp;gt; ((ISearchContent)x).SearchPublishDate.ToString());
            }            

            return query
                .Take(maxResults).SetTimeout(10000);
        }

        private IEnumerable&amp;lt;Type&amp;gt; GetContentTypesFromQuery(string parameter, Query query)
        {
            if (query.Parameters.ContainsKey(parameter))
            {
                var array = query.Parameters[parameter] as JArray;
                if (array != null)
                {
                    return array.Values&amp;lt;string&amp;gt;().SelectMany(GetContentTypes);
                }
            }
            return Enumerable.Empty&amp;lt;Type&amp;gt;();
        }

        private new IEnumerable&amp;lt;Type&amp;gt; GetContentTypes(string allowedType)
        {
            var uiDescriptor = _uiDescriptorRegistry.UIDescriptors.FirstOrDefault(d =&amp;gt; d.TypeIdentifier.Equals(allowedType, StringComparison.OrdinalIgnoreCase));
            if (uiDescriptor == null)
                return Enumerable.Empty&amp;lt;Type&amp;gt;();

            return _contentTypeRepository
                .List()
                .Where(c =&amp;gt; uiDescriptor.ForType.IsAssignableFrom(c.ModelType))
                .Select(c =&amp;gt; c.ModelType);
        }

        private Language ResolveSupportedLanguageBasedOnPreferredCulture()
        {
            Language language = null;
            var preferredCulture = ContentLanguage.PreferredCulture;
            if (preferredCulture != null)
            {
                language = _searchClient.Settings.Languages.GetSupportedLanguage(preferredCulture);
            }
            language = language ?? Language.None;
            return language;
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2025-06-12T11:45:01.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Avoid Field Type Conflicts in Search &amp; Navigation</title><link href="https://world.optimizely.com/blogs/dada/dates/2025/6/avoid-field-type-conflicts-in-optimizely-search--navigation/" /><id>&lt;p&gt;When using&amp;nbsp;&lt;strong&gt;Optimizely Search &amp;amp; Navigation&lt;/strong&gt;, reusing the same property name across different content types is fine &amp;mdash; &lt;strong&gt;as long as the type is consistent&lt;/strong&gt;. If one type uses int and another uses string, you&#39;ll run into indexing and query issues.&lt;/p&gt;
&lt;h2&gt;&#128165; The Issue&lt;/h2&gt;
&lt;p&gt;Optimizely Search &amp;amp; Navigation stores data in &lt;strong&gt;Elasticsearch&lt;/strong&gt;, which assigns a data type to each field the first time it sees it. That type is then fixed.&lt;/p&gt;
&lt;p&gt;If CategoryId is first indexed as an int, any later document sending &quot;12&quot; (as a string) under the same field name causes a conflict.&lt;/p&gt;
&lt;p&gt;This can lead to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Missing / fluctuating search results&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Filters not working&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Incorrect aggregations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Indexing errors&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&#129514; Example&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public virtual List&amp;lt;int&amp;gt; CategoryId { get; set; }  // Used in one type
public virtual List&amp;lt;string&amp;gt; CategoryId { get; set; }  // Used in another
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;term&quot;: { &quot;CategoryId&quot;: 12 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Only matches documents where CategoryId is an integer.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;✅ How to Prevent It&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Keep the &lt;strong&gt;same type&lt;/strong&gt; for shared field names across all content types - at least for those content types you will be indexing.&lt;br /&gt;&lt;br /&gt;It can be good to know that this is not always an issue because we do suffix many types (string gets the suffix $$string) meaning a plain string property and an integer propery will be keyed differently in the mappings.&amp;nbsp;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&#129521; Root Cause&lt;/h2&gt;
&lt;p&gt;This is a &lt;strong&gt;limitation in Elasticsearch&lt;/strong&gt;, not a bug in Optimizely Search &amp;amp; Navigation. Once a field is mapped with one type, it can&amp;rsquo;t accept another.&lt;/p&gt;
&lt;h2&gt;✅ Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Same name + different type = broken queries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Align types for all shared property names&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use consistent modeling to avoid mapping conflicts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</id><updated>2025-06-05T09:21:59.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Avoid Deep Indexing of ContentAreas Unless Necessary</title><link href="https://world.optimizely.com/blogs/dada/dates/2025/6/avoid-deep-indexing-of-contentareas-unless-necessary/" /><id>&lt;p&gt;In a typical Episerver CMS implementation, it&amp;rsquo;s common to encounter deeply nested structures &amp;mdash; ContentAreas containing Blocks, which themselves contain more ContentAreas and Blocks, potentially going multiple levels deep.&lt;/p&gt;
&lt;h2&gt;The Problem with Deep Nesting in Indexing&lt;/h2&gt;
&lt;p&gt;By default, &lt;a href=&quot;/link/9fa95469dbdd44cea3ed19aaf7e2a1cf.aspx&quot;&gt;when indexing ContentAreas&lt;/a&gt;, there&amp;rsquo;s no sensible maximum nesting depth enforced. Only the default JSON serialization max depth at 25.This can lead to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Increased strain on the search service&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Longer indexing times&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Larger mapping sizes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Degraded query performance&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don&amp;rsquo;t specifically need to filter or retrieve deeply nested content, it&amp;rsquo;s a good idea to limit how deep the indexing goes.&lt;/p&gt;
&lt;h2&gt;How to Limit ContentArea Depth&lt;/h2&gt;
&lt;p&gt;This can be controlled using the MaxDepthContentAreaConverter. You can configure the maximum depth by adding the following code to your initialization module:&lt;/p&gt;
&lt;div class=&quot;contain-inline-size rounded-md border-[0.5px] border-token-border-medium relative bg-token-sidebar-surface-primary&quot;&gt;
&lt;div class=&quot;overflow-y-auto p-4&quot;&gt;&lt;code class=&quot;whitespace-pre! language-csharp&quot;&gt;SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;ContentArea&amp;gt;().ModifyContract(x =&amp;gt; x.Converter = &lt;span class=&quot;hljs-keyword&quot;&gt;new&lt;/span&gt; MaxDepthContentAreaConverter(&lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;));
&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This example limits the indexing depth to 1 level, but you can adjust the value based on your needs.&lt;br /&gt;&lt;br /&gt;This information is also available in our documentation:&lt;br /&gt;https://world.optimizely.com/documentation/Items/Developers-Guide/EPiServer-Find/11/Integration/episerver-77-5-cms/Indexing-content-in-a-content-area/&lt;/p&gt;
&lt;h2&gt;Important Note on SearchText&lt;/h2&gt;
&lt;p&gt;Keep in mind, this setting only affects the serialization depth of ContentAreas for indexing purposes. It does not impact the maximum depth used by SearchText, which is responsible for collecting, concatenating, and indexing textual content within ContentAreas and their nested items. Meaning the content in your ContentArea will still be searchable.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2025-06-04T11:23:02.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Optimize your queries for performance and search relevance</title><link href="https://world.optimizely.com/blogs/dada/dates/2025/6/optimize-your-queries-for-performance-and-search-relevance/" /><id>&lt;p&gt;When working with Search &amp;amp; Navigation, query optimization can have a massive impact on both performance and search quality. Here are some practical tips to help you tune your queries for better speed, lower resource usage, and more relevant search results.&lt;/p&gt;
&lt;h2&gt;Use single terms query instead of multiple term OR queries&lt;/h2&gt;
&lt;p&gt;If you&#39;re filtering on the same field with multiple term OR conditions, switch to using a terms filter. It&amp;rsquo;s more efficient and easier to maintain.&lt;br /&gt;In this code this translates to the following:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;❌ Bad:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.Filter(x =&amp;gt; x.ModelSize.Match(&quot;S&quot;) | x.ModelSize.Match(&quot;M&quot;) | x.ModelSize.Match(&quot;L&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;✅ Good:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;.Filter(x =&amp;gt; x.ModelSize.In(new[] { &quot;S&quot;, &quot;M&quot;, &quot;L&quot; }))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;The terms filter is&amp;nbsp;&lt;strong&gt;cached&lt;/strong&gt; in Elasticsearch. This significantly reduces CPU and memory load, speeds up queries, and minimizes shard access.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Improve Relevance by Boosting Exact Matches&lt;/h2&gt;
&lt;p&gt;By default, queries may return results based on stemmed words, synonyms, or even compound terms. To improve relevance, you can explicitly&amp;nbsp;&lt;strong&gt;boost exact matches&lt;/strong&gt; using a custom extension method like InStandardField() below.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public static class QueryStringSearchExtensions
{
    public static IQueriedSearch&amp;lt;TSource, QueryStringQuery&amp;gt; InStandardField&amp;lt;TSource, TExistingQuery&amp;gt;(
        this IQueriedSearch&amp;lt;TSource, TExistingQuery&amp;gt; search,
        Expression&amp;lt;Func&amp;lt;TSource, string&amp;gt;&amp;gt; fieldSelector,
        double? relativeImportance = null)
        where TExistingQuery : QueryStringQuery
    {
        fieldSelector.ValidateNotNullArgument(&quot;fieldSelector&quot;);
        return search.InField(
            search.Client.Conventions.FieldNameConvention.GetFieldName((Expression)fieldSelector),
            relativeImportance);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;    .InStandardField(x =&amp;gt; x.Name, 5)         // Boost exact match by factor of 5
    .InStandardField(x =&amp;gt; x.SearchText, 5)   // Boost exact match by factor of 5
    .InField(x =&amp;gt; x.Name)                    // Standard search
    .InField(x =&amp;gt; x.SearchText);             // Standard search
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Example: Searching for &quot;vei&quot;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Before Workaround (No boosting):&lt;/strong&gt;&lt;br /&gt;Many of the matches are compound words or unrelated stems.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;Karriereveiledningskonferansen&quot;&lt;br /&gt;&quot;Vegstrategi&quot;&lt;br /&gt;&quot;Vegforvaltning&quot;&lt;br /&gt;&quot;Fylkesveg&quot;&lt;br /&gt;&quot;Elektrisk vei&quot;&lt;br /&gt;&quot;Vegstrategi&quot;&lt;br /&gt;&quot;Delstrategi veg&quot;&lt;br /&gt;&quot;Nyheter fylkesveg&quot;&lt;br /&gt;&quot;Vegamot AS&quot;&lt;br /&gt;&quot;Stillinger veg&quot;&lt;br /&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After Workaround (Boosted relevance):&lt;/strong&gt;&lt;br /&gt;Results now prioritize exact matches:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;Elektrisk vei&quot;&lt;br /&gt;&quot;Delstrategi veg&quot;&lt;br /&gt;&quot;Stillinger veg&quot;&lt;br /&gt;&quot;Kontaktinformasjon veg&quot;&lt;br /&gt;&quot;Veg-prosjekter&quot;&lt;br /&gt;&quot;H&amp;oslash;ringssvar delstrategi veg&quot;&lt;br /&gt;&quot;S&amp;oslash;knad om reklame langs veg&quot;&lt;br /&gt;&quot;Fravik fra krav i vegnormal&quot;&lt;br /&gt;&quot;Tr&amp;oslash;ndelagsmodellen - skreddersydd veg til sikker jobb&quot;&lt;br /&gt;&quot;Stenger veg i Overhalla&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the relevance of results is dramatically improved. Exact matches like &quot;veg&quot; are ranked higher, and compound or less-relevant terms are deprioritized.&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace term OR logic on the same field with terms filters to improve query performance.&lt;/li&gt;
&lt;li&gt;Use field boosting via extensions like InStandardField() to elevate exact matches over stemmed, synonyms or compound terms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By following these patterns, you&#39;ll get faster queries and better results &amp;mdash; a win-win for both backend performance and user satisfaction.&lt;/p&gt;</id><updated>2025-06-04T08:52:42.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Searching Breadcrumbs and Content and their Assets</title><link href="https://world.optimizely.com/blogs/dada/dates/2024/1/searching-content-and-assets-below-a-specific-node/" /><id>&lt;p&gt;Recently, I handled a couple of support cases that revolved around two challenges in a Search &amp;amp; Navigation.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rendering breadcrumbs for search results&lt;/strong&gt;, and&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Searching below a specific node and including content &lt;em&gt;and&lt;/em&gt; content assets&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither problem was rocket science, but after digging in, testing, and writing some code, we ended up with ideas worth sharing. Hopefully, they&amp;rsquo;ll save you some time if you&amp;rsquo;re tackling something similar.&lt;/p&gt;
&lt;h2&gt;Case #1: Searching Below a Specific Node (And Including Assets)&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you&#39;re searching under a specific node (e.g. a section or page on your site). You want to include &lt;strong&gt;both pages and content assets (like PDFs, images, etc.)&lt;/strong&gt; stored below this node.&lt;/p&gt;
&lt;p&gt;You might start with something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var result = SearchClient.Instance.UnifiedSearch()
    .For(q)
    .Filter(x =&amp;gt; ((IContent)x).Ancestors().Match(node_to_search_below))
    .GetResult();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works perfectly for &lt;strong&gt;content pages&lt;/strong&gt;, but not for &lt;strong&gt;content assets&lt;/strong&gt;. Why?&lt;/p&gt;
&lt;h3&gt;&#129504; Why Assets Are Missing&lt;/h3&gt;
&lt;p&gt;Assets are stored in a different structure than pages. Their Ancestors() path does not include the node you&#39;re filtering on. This is because assets like documents are typically stored in Asset Folders, which are often detached from your actual content hierarchy.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;✅ Solution: Index Owner Ancestors for Assets&lt;/h3&gt;
&lt;p&gt;We can solve this by populating a virtual field with the ancestors of the &lt;strong&gt;owning content&lt;/strong&gt; for each asset. That way, assets &quot;inherit&quot; the path of their parent content at index time.&lt;/p&gt;
&lt;h4&gt;Step 1: Extend MediaData With Owner Ancestors&lt;/h4&gt;
&lt;p&gt;In your &lt;strong&gt;Find initialization module&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;MediaData&amp;gt;()
    .IncludeField(x =&amp;gt; x.ContentAncestors());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, add this helper extension method:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public static IEnumerable&amp;lt;string&amp;gt; ContentAncestors(this MediaData content)
{
    var contentRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
    var contentAssetHelper = ServiceLocator.Current.GetInstance&amp;lt;ContentAssetHelper&amp;gt;();

    var assetOwner = contentAssetHelper.GetAssetOwner(content.ContentLink);
    if (assetOwner != null)
    {
        var assetContent = contentRepository.Get&amp;lt;IContent&amp;gt;(assetOwner.ContentLink) as PageData;
        return assetContent?.Ancestors();
    }

    return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code retrieves the owning page of the asset and gets its Ancestors() so we can treat the asset as if it were part of the same structure.&lt;/p&gt;
&lt;hr /&gt;
&lt;h4&gt;Step 2: Update Your Search Query&lt;/h4&gt;
&lt;p&gt;Now, combine the original ancestor filter with your new one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var result = SearchClient.Instance.UnifiedSearch()
    .For(q)
    .Filter(x =&amp;gt; ((IContent)x).Ancestors().Match(node_to_search_below)
             || ((MediaData)x).ContentAncestors().Match(node_to_search_below))
    .GetResult();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Voil&amp;agrave; &amp;mdash; you now get results for&amp;nbsp;&lt;strong&gt;both pages and their related assets&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Case #2: Generating Breadcrumbs from Search Results&lt;/h2&gt;
&lt;p&gt;Breadcrumbs are often generated based on Ancestors(). While it&amp;rsquo;s possible to resolve these at render time, a more efficient approach is to index ancestor names so they&amp;rsquo;re available immediately from the search index.&lt;/p&gt;
&lt;h3&gt;✅ Solution: Index Ancestor Names&lt;/h3&gt;
&lt;p&gt;In your Find initialization code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;IContent&amp;gt;()
    .IncludeField(x =&amp;gt; x.AncestorNames());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then define the extension like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public static IEnumerable&amp;lt;string&amp;gt; AncestorNames(this IContent content)
{
    var contentRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
    var ancestorNames = new List&amp;lt;string&amp;gt;();

    foreach (var ancestorContentRef in content.Ancestors())
    {
        var ancestorPage = contentRepository.Get&amp;lt;IContent&amp;gt;(new ContentReference(ancestorContentRef)) as PageData;
        if (ancestorPage != null)
        {
            ancestorNames.Add(ancestorPage.Name);
        }
    }

    return ancestorNames;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, your search results come with a breadcrumb trail like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;AncestorNames&quot;: [&quot;Start&quot;, &quot;About Us&quot;, &quot;Team&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use this directly to render navigation or path-based UX without additional lookups.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2025-06-04T08:50:28.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Improve performance by enabling language routing on search requests</title><link href="https://world.optimizely.com/blogs/dada/dates/2023/4/enable-language-routing-for-search-requests/" /><id>&lt;p&gt;In Find 13, we &lt;a href=&quot;/link/a60589764eca488ab205842da0714609.aspx&quot;&gt;introduced language routing on indexing&lt;/a&gt; to improve indexing performance for indexes setup with multiple languages.&lt;/p&gt;
&lt;p&gt;With the release of Find 13.4.9, we further introduced language routing on query time. We recommend moving up to &lt;span&gt;13.5.7 due to other bug fixes. This feature is also available in .NET flavours: 14.2.4 and 15.0.3&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This feature enables passing the query only to the specific language index, rather than having all language indexes process the query even though most of them would only respond with 0 hits as we&#39;re usually filtering on language properties in the index.&lt;/p&gt;
&lt;p&gt;Since 13.5.1 this feature is disabled by default.&lt;br /&gt;&lt;br /&gt;To enable this feature, there are a few steps to follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure that you explicitly or implicitly set the language for your query.&lt;br /&gt;You typically achieve this by using one of the following approaches:&lt;br /&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;.Search&amp;lt;type&amp;gt;(language) or .UnifiedSearch(language)&lt;/code&gt;&lt;/pre&gt;
Alternatively, you can rely on the extensions provided by &lt;em&gt;Episerver.Find.CMS&lt;/em&gt; and use &lt;br /&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;.UnifiedSearch() or .UnifiedSearchFor()&lt;/code&gt;&lt;/pre&gt;
which will automatically set the preferred culture if you don&#39;t specify one. Be careful not to use the &lt;em&gt;Episerver.Find&lt;/em&gt; extensions, as they do not resolve language.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;You can verify that everything is working correctly by checking the analyzer set in the JSON for the request against the &lt;em&gt;_search.&lt;/em&gt;&amp;nbsp;&lt;br /&gt;It should say &lt;em&gt;analyzer:[language]. &lt;/em&gt;If you are using &lt;em&gt;.UsingSynonyms()&lt;/em&gt; it should say&amp;nbsp;&lt;em&gt;analyzer: synonym_[language_code]&lt;/em&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;If you are using CMS 11, you can do this in the web.config file by adding the following:&lt;br /&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;&amp;lt;appSettings&amp;gt;
    &amp;lt;add key=&quot;episerver:EnableLanguageRoutingInSearchRequest&quot; value=&quot;true&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
If you are using CMS 12, you can do this in the appsettings.json file by adding the following:&lt;br /&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;&quot;EPiServer&quot;: {
    &quot;Find&quot;: {
      &quot;EnableLanguageRoutingInSearchRequest&quot;:true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;br /&gt;Note that if you have custom InField extension that makes it possible to query fields for multiple language fields, language routing query time cannot be used.&lt;/p&gt;
&lt;p&gt;Our tests have shown a &lt;span style=&quot;text-decoration:&amp;#32;underline;&quot;&gt;decrease in response times of around 25% &lt;/span&gt;for queries with language routing enabled.&amp;nbsp;&lt;br /&gt;However, this improvement may vary depending on your implementation, the number of languages enabled on the index, and the data indexed.&lt;br /&gt;&lt;br /&gt;Find versions that support language routing on search requests&lt;br /&gt;&lt;a href=&quot;https://nuget.optimizely.com/package/?id=EPiServer.Find&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;v=15.0.3&quot;&gt;https://nuget.optimizely.com/package/?id=EPiServer.Find&amp;amp;v=15.0.3&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://nuget.optimizely.com/package/?id=EPiServer.Find&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;v=14.2.4&quot;&gt;https://nuget.optimizely.com/package/?id=EPiServer.Find&amp;amp;v=14.2.4&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://nuget.optimizely.com/package/?id=EPiServer.Find&amp;amp;amp;amp;amp;v=13.5.7&quot;&gt;https://nuget.optimizely.com/package/?id=EPiServer.Find&amp;amp;v=13.5.7&lt;/a&gt;&lt;/p&gt;</id><updated>2023-04-03T15:16:32.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Unlisting Episerver.Find packages 13.4.9-13.5.1 and 14.1.0-14.2.2</title><link href="https://world.optimizely.com/blogs/dada/dates/2023/1/unlisting-episerver-find-packages-13-4-9-13-5-1-and-14-1-0-14-2-2/" /><id>&lt;p&gt;Dear partners and developers,&lt;/p&gt;
&lt;p&gt;Due to issues introduced with this change/release &lt;a href=&quot;/link/76974ad8d2a84c1b989ad0ac453ab663.aspx?releaseNoteId=FIND-5191&amp;amp;epsremainingpath=ReleaseNote/&quot;&gt;https://world.optimizely.com/documentation/Release-Notes/ReleaseNote/?releaseNoteId=FIND-5191&lt;/a&gt;&lt;br /&gt;which impacts both clients and the Find service we are delisting the following Episerver.Find.* package from our NuGet feed with immidate effect with the exception of Episerver.Find.Commerce.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;13.4.9, 13.5.1, 14.1.0, 14.2.1, 14.2.2&lt;br /&gt;
&lt;p&gt;If you&#39;re on any of these versions mentioned above make you sure you upgrade.&lt;br /&gt;Note that unlisting packages will still let you install them if you&#39;re already referencing them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These issues caused the following symptoms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updating content with child content often incorrectly causes child content to get reindexed which&lt;br /&gt;consumes unneccessary resources from both web app and Find cluster.&lt;/li&gt;
&lt;li&gt;Delays in updates i.e. delete, save and publishing, to reflect in the Find index due to indexing queue growing large.&lt;/li&gt;
&lt;li&gt;Performance issues in the CMS UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These issues were fixed with releases 13.5.2 and 14.2.4.&lt;br /&gt;More information available here &lt;a href=&quot;/link/76974ad8d2a84c1b989ad0ac453ab663.aspx?releaseNoteId=FIND-10852&amp;amp;epsremainingpath=ReleaseNote/&quot;&gt;https://world.optimizely.com/documentation/Release-Notes/ReleaseNote/?releaseNoteId=FIND-10852&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</id><updated>2023-01-17T13:29:21.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Configure your own Search &amp; Navigation timeouts</title><link href="https://world.optimizely.com/blogs/dada/dates/2022/1/find-requests-and-timeouts/" /><id>&lt;p&gt;Changing the timeout for Search &amp;amp; Navigation requests is something that has been requested over and over in forums and support cases for quite some time. &lt;br /&gt;The default request timeout is set to &lt;span&gt;100 seconds (HttpWebRequest.Timeout default) which could be problematic when requests takes longer than expected.&lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;You might know that you can set the request timeout in configuration&lt;/h3&gt;
&lt;p&gt;This will be used for all types of Find requests and could pose a problem if you want to lower it to 10 secs but at the same time not affect requests that should be allowed to run longer e.g bulk requests.&lt;/p&gt;
&lt;p&gt;Timeout is set in milliseconds.&lt;/p&gt;
&lt;p&gt;CMS 11 and lower&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;&amp;lt;appSettings&amp;gt;&amp;lt;add key=&quot;episerver:FindDefaultRequestTimeout&quot; value=&quot;30000&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CMS 12&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;   &quot;AppSettings&quot;: {
        &quot;episerver:FindDefaultRequestTimeout&quot;: 30000
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More on configuration here &lt;a href=&quot;/link/20894744d88447cf8986cd46db0b543b.aspx&quot;&gt;https://world.optimizely.com/documentation/developer-guides/CMS/configuration/&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;But it&#39;s also possible with some code to set the timeout for a specific request&lt;/h3&gt;
&lt;p&gt;Add the following extension methods&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public static ITypeSearch&amp;lt;TResult&amp;gt; SetTimeout&amp;lt;TResult&amp;gt;(this ITypeSearch&amp;lt;TResult&amp;gt; search, int timeout)
{
    return new Search&amp;lt;TResult, IQuery&amp;gt;(search, context =&amp;gt;
    {
        var existingAction = context.CommandAction;
        context.CommandAction = command =&amp;gt;
        {
            if (existingAction.IsNotNull())
            {
                existingAction(command);
            }
            command.ExplicitRequestTimeout = timeout;
        };

    });
}

public static ISearch&amp;lt;TResult&amp;gt; SetTimeout&amp;lt;TResult&amp;gt;(this ISearch&amp;lt;TResult&amp;gt; search, int timeout)
{
    return new Search&amp;lt;TResult, IQuery&amp;gt;(search, context =&amp;gt;
    {
        var existingAction = context.CommandAction;
        context.CommandAction = command =&amp;gt;
        {
            if (existingAction.IsNotNull())
            {
                existingAction(command);
            }
            command.ExplicitRequestTimeout = timeout;
        };

    });
}

public static IMultiSearch&amp;lt;TResult&amp;gt; SetTimeout&amp;lt;TResult&amp;gt;(this IMultiSearch&amp;lt;TResult&amp;gt; multiSearch, int timeout)
{
    var searches = new List&amp;lt;ISearch&amp;lt;TResult&amp;gt;&amp;gt;(multiSearch.Searches);
    multiSearch.Searches.Clear();
    foreach (var search in searches)
    {
        multiSearch.Searches.Add(SetTimeout(search, timeout));
    }
    return multiSearch;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And use it like this&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var results = SearchClient.Instance
  .UnifiedSearch
  .For(&quot;random fruit&quot;)
  .SetTimeout(10000)
  .GetResult();

var results = searchClient.Search&amp;lt;Fruits&amp;gt;
  .For(&quot;banana&quot;)
  .SetTimeout(1000)
  .GetResult();

var results = searchClient.MultiSearch&amp;lt;Fruits&amp;gt;()
  .Search&amp;lt;Exotic&amp;gt;(x =&amp;gt; x.For(&quot;Kiwi&quot;).InField(y =&amp;gt; y.SearchTitle()))
  .Search&amp;lt;Ordinary&amp;gt;(x =&amp;gt; x.For(&quot;Apple&quot;).InField(y =&amp;gt; y.SearchTitle()))
  .SetTimeout(1000)
  .GetResult();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;Make sure you catch your timeouts. They will throw a ServiceException.&lt;br /&gt;More on Find exceptions you should consider catching is available in Jonas Bergqvist&#39;s blog post &lt;a href=&quot;/link/3a36ccb7fc0e485194070a4b38adfb44.aspx&quot;&gt;Exceptions in find&amp;nbsp;&lt;/a&gt;&lt;/p&gt;</id><updated>2022-01-28T14:44:10.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>How to make Find 13.x play nice with Find on-premise</title><link href="https://world.optimizely.com/blogs/dada/dates/2020/10/how-to-make-find-13-x-play-nice-with-find-on-premise/" /><id>&lt;p&gt;If you are using Find (Search &amp;amp; Navigation) 13.x or later with an index located on a Find on-premise instance you need to do the following to achieve full compatibility.&amp;nbsp;&lt;br /&gt;This is &lt;span style=&quot;text-decoration:&amp;#32;underline;&quot;&gt;&lt;span style=&quot;color:&amp;#32;#000000;&amp;#32;text-decoration:&amp;#32;underline;&quot;&gt;not&lt;/span&gt;&lt;/span&gt; applicable to any solution where you&#39;re using an index hosted by EPiServer.&lt;/p&gt;
&lt;p&gt;1. If you are on 13.0.5 or later you need to make sure you upgrade to at least 13.2.4 and add&amp;nbsp;&lt;em&gt;useLegacySorting=true&lt;/em&gt; to your episerver.find config element to be&lt;br /&gt;&amp;nbsp; &amp;nbsp; able to use the Elastic Search sorting functionality to it&#39;s full extent.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;lt;episerver.find serviceurl=&quot;...&quot; useLegacySorting=&quot;true&quot; ... &amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;2. With Find 13 language routing was introduced. This is not available on Find on-premise therefor you should disable it.&lt;br /&gt;&amp;nbsp; &amp;nbsp; Disable Language Routing support via LanguageRoutingFactory.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;   [InitializableModule]
   [ModuleDependency(typeof(IndexingModule))]
   public class MyFindInitializationModule : IConfigurableModule
   {
       public void Initialize(InitializationEngine context)
       {
       }
       public void Uninitialize(InitializationEngine context)
       {
       }
       public void ConfigureContainer(ServiceConfigurationContext context)
       {
           context.Services.AddSingleton&amp;lt;LanguageRoutingFactory, MyLanguageRoutingFactory&amp;gt;();
       }
       }

   public class MyLanguageRoutingFactory : LanguageRoutingFactory
   {
       public override LanguageRouting CreateLanguageRouting(ILocale locale)
       {
           return null;
       }
   }
   }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;</id><updated>2020-10-13T10:12:29.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Improving synonyms and overall search experience</title><link href="https://world.optimizely.com/blogs/dada/dates/2020/6/improving-synonyms-and-overall-search-experience/" /><id>&lt;p&gt;Is your Search &amp;amp; Navigation (Find) implementation affected by the limitations of the current synonym functionality and/or would you like to improve upon the overall search experience?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Check out&amp;nbsp;&lt;a href=&quot;https://github.com/episerver/EPiServer.Labs.Find.Toolbox&quot;&gt;https://github.com/episerver/EPiServer.Labs.Find.Toolbox&lt;/a&gt;&lt;br /&gt;Setup and configuration is simple. Install NuGet Package and then there are a couple of lines of code to get it working.&lt;/p&gt;
&lt;p&gt;What you get&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An improved synonym implementation (No more issues with synonyms in conjuction with .WithAndAsDefaultOperator)&lt;/li&gt;
&lt;li&gt;An overall relevance improvement by utilising MatchPhrase, MatchPhrasePrefix and MatchPrefix&lt;/li&gt;
&lt;li&gt;Support for MinimumShouldMatch which improves search experience further&lt;/li&gt;
&lt;li&gt;FuzzyMatch and WildcardMatch improving searches with typos and partial words&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All can be used together or independently and depends on the .For() call for the original query&lt;/p&gt;
&lt;p&gt;Full details available in README&amp;nbsp;&lt;br /&gt;https://github.com/episerver/EPiServer.Labs.Find.Toolbox/blob/master/README.md&lt;/p&gt;
&lt;p&gt;Feedback and input are welcome and don&#39;t hesitate to contribute if you&#39;d like.&lt;/p&gt;
&lt;p&gt;Please note that this project is not officially supported by Episerver just like most EPiServer.Labs projects.&lt;br /&gt;Should be considered stable and is currently used in production environments.&lt;strong&gt;&lt;em&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;UPDATE 2021-01-09&lt;br /&gt;&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;New version 1.1.1.&lt;/em&gt;&lt;em&gt;&lt;br /&gt;&lt;/em&gt;&lt;em&gt;Bugfix for bidrectional multi term synonyms&lt;br /&gt;Boost adjustment for PrefixQueries&lt;br /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;UPDATE 2020-10-14&lt;br /&gt;&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;New version 1.1.0.&lt;br /&gt;&lt;/em&gt;&lt;em&gt;Further improvements to relevance with the addition of .UsingRelevanceImprove() &lt;br /&gt;&lt;/em&gt;&lt;em&gt;which combines the powers of Elastic Search&#39;s MatchPhrase, MatchPhrasePrefix and MatchPrefix.&lt;br /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;UPDATE 2020-09-08&lt;br /&gt;&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;Bugfixes and improvements and new version&amp;nbsp;1.0.9.&lt;br /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;UPDATE 2020-09-03&lt;br /&gt;&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;Project renamed to Find Toolbox @&amp;nbsp;&lt;a href=&quot;https://github.com/episerver/EPiServer.Labs.Find.Toolbox&quot;&gt;https://github.com/episerver/EPiServer.Labs.Find.Toolbox&lt;/a&gt;&lt;br /&gt;&lt;/em&gt;&lt;em&gt;Now with new features to improve search relevance and overall search experience even further.&amp;nbsp;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;</id><updated>2020-06-24T19:14:45.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Why is my Find indexing job freezing and dying?</title><link href="https://world.optimizely.com/blogs/dada/dates/2020/4/why-is-my-find-indexing-job-freezing-and-dying/" /><id>&lt;p&gt;I recently had a support case which I spent maybe a few hours too many on.&lt;/p&gt;
&lt;p&gt;The symptom was a find indexing job running for about 5 minutes, web app froze up and restarted causing the indexing job to fail prematurly. After looking into application logs, cpu and memory usage (which looked good overall) we could see the app were being restarted .&lt;/p&gt;
&lt;p&gt;We did profiling with some hints that the scheduled job thread was aborted.&amp;nbsp;We then took a memory dump 4 minutes into the indexing job.&lt;/p&gt;
&lt;p&gt;In the memory dump we could see StackOverflowException, OutOfMemoryException and ThreadAbortException.&lt;br /&gt;After some more digging we found a call to a custom IsSearchable() for the same content over and over. &lt;br /&gt;This behaviour was due to self-referencing content and some custom logic that made it index the same content over and over.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;For future cases I&#39;m gonna use something like below to more easily identify errors like this without profiling or memory dumps.&lt;br /&gt;Run the indexing job and check the logs for&amp;nbsp;&#39;This content was recently indexed&#39; and it might give you a hint on what it&#39;s getting stuck on.&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class InitializationModule2 : IInitializableModule
{

protected static readonly ILog _logger = LogManager.GetLogger(typeof(ContentIndexer));

public void Initialize(InitializationEngine context)
{

    var previouslyIndexedObjects = new Queue&amp;lt;int&amp;gt;();
    int maxLookBack = 1000;

    ContentIndexer.Instance.Conventions.ForInstancesOf&amp;lt;IContent&amp;gt;().ShouldIndex(x =&amp;gt;
    {

        _logger.Info(string.Format(&quot;Attempting to index content ID: {0 } Name: {1}&quot;, x.ContentLink.ID.ToString(), x.Name.ToString()));

        if (previouslyIndexedObjects.Contains(x.ContentLink.ID))
        {
            _logger.Error(string.Format(&quot;WARNING! This content was recently indexed: {0 } Name: {1}&quot;, x.ContentLink.ID.ToString(), x.Name.ToString()));
        }

        if (previouslyIndexedObjects.Count() == maxLookBack) { previouslyIndexedObjects.Dequeue(); }
        previouslyIndexedObjects.Enqueue(x.ContentLink.ID);

    return true;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;br /&gt;&lt;/code&gt;...&lt;/p&gt;</id><updated>2020-04-03T11:01:10.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Common Find caching pitfalls</title><link href="https://world.optimizely.com/blogs/dada/dates/2019/1/common-find-caching-pitfalls/" /><id>&lt;p&gt;&lt;span&gt;Under certain conditions, the cache duration set with&amp;nbsp;&lt;/span&gt;&lt;span&gt;.StaticallyCacheFor(minutes)&lt;/span&gt;&lt;span&gt;&amp;nbsp;does not last as long as specified.&lt;br /&gt;&lt;/span&gt;&lt;span&gt;There are three common reasons for this.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;More on .StatisticallyCacheFor can be found here&lt;br /&gt;&lt;a href=&quot;/link/ad2f9694543c42c0a54a11bf2a76d2ec.aspx&quot;&gt;https://world.episerver.com/documentation/developer-guides/find/NET-Client-API/searching/Caching/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3&gt;.Filter() on a datetime property&lt;/h3&gt;
&lt;p&gt;Any .Filter() on a datetime property with second (or finer) resolution should be rounded up to the nearest minutes matching the wanted cache duration. As the cache key is based on the query payload we don&#39;t want it to change more frequently than the cache duration otherwise we will rarely utilise the cache as a new cache is generated for every request and a new request sent to the Find service.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;Here is an example of such a filter extracted from the JSON sent to the _search endpoint. You could inspect your requests with &lt;a href=&quot;https://www.telerik.com/fiddler&quot;&gt;Fiddler&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&quot;range&quot;: {&lt;br /&gt;&quot;EndDate$$date&quot;: {&lt;br /&gt;&quot;from&quot;: &quot;2019-01-24T20:59:59Z&quot;,&lt;br /&gt;&quot;to&quot;: &quot;9999-12-31T23:59:59.9999999Z&quot;,&lt;br /&gt;&quot;include_lower&quot;: false,&lt;br /&gt;&quot;include_upper&quot;: true&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;
&lt;h3&gt;&lt;br /&gt;.FilterForVisitor()&lt;/h3&gt;
&lt;p&gt;.FilterForVisitor is a set of filters including .ExcludeDelete, .PublishedInCurrentLanguage, and .FilterOnReadAccess.&lt;/p&gt;
&lt;p&gt;.PublishedInCurrentLanguage contains a filter on a datetime and as such is affected by the same issue described in point #1.&lt;/p&gt;
&lt;p&gt;In a later version of Find (13.1) the datetime sent &lt;span&gt;through&amp;nbsp;&lt;/span&gt;to the .PublishedInCurrentLanguage() method uses a NOW() function instead of an actual value and is therefor not&amp;nbsp;affected by this.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;If on an earlier version, replace&amp;nbsp;&lt;/span&gt;.FilterForVisitor() with the individual filters and round up the datetime used with PublishedInCurrentLanguage() just as you would have if you&#39;ve used a custom datetime filter as pointed out earlier.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3&gt;.GetContentResult()&lt;/h3&gt;
&lt;p&gt;When using .GetContentResult() unlike .GetResult() there is a cache dependency on published content. If there are any updates cache will be evicted. For Commerce sites &lt;span&gt;updates are &lt;/span&gt;&lt;span&gt;typically frequent&lt;/span&gt;&amp;nbsp;and could therefore have a negative impact on cache.&lt;/p&gt;
&lt;p&gt;Our recommendation is to use a custom .GetContentResult() where cache with this dependency is bypassed. This is commented out here.&lt;br /&gt;Don&#39;t forget to add your .StaticallyCacheFor() to control the cache duration.&lt;/p&gt;
&lt;pre&gt;&lt;br /&gt;public static IContentResult&amp;lt;TContentData&amp;gt; GetContentResult&amp;lt;TContentData&amp;gt;(this ITypeSearch&amp;lt;TContentData&amp;gt; search, int cacheForSeconds = 60, bool cacheForEditorsAndAdmins = false)&lt;br /&gt;where TContentData : IContentData&lt;br /&gt;{&lt;br /&gt;if (typeof(IContent).IsAssignableFrom(typeof(IContent)))&lt;br /&gt;{&lt;br /&gt;search = search.Filter(x =&amp;gt; ((IContentData)x).MatchTypeHierarchy(typeof(IContent)));&lt;br /&gt;}&lt;br /&gt;var projectedSearch = search&lt;br /&gt;.Select(x =&amp;gt;&lt;br /&gt;new ContentInLanguageReference(&lt;br /&gt;new ContentReference(((IContent)x).ContentLink.ID, ((IContent)x).ContentLink.ProviderName),&lt;br /&gt;((ILocalizable) x).Language.Name));&lt;br /&gt;&lt;br /&gt;//Apply caching for non-editors&lt;br /&gt;//if (cacheForEditorsAndAdmins || !(PrincipalInfo.HasEditAccess || PrincipalInfo.HasAdminAccess))&lt;br /&gt;//{&lt;br /&gt;// var cacheSettings = ServiceLocator.Current.GetInstance&amp;lt;IContentCacheKeyCreator&amp;gt;();&lt;br /&gt;// projectedSearch = projectedSearch.StaticallyCacheFor(TimeSpan.FromSeconds(cacheForSeconds), new CacheDependency(null, new string[] { cacheSettings.RootKeyName }));&lt;br /&gt;//}&lt;br /&gt;&lt;br /&gt;var searchResult = projectedSearch.GetResult();&lt;br /&gt;&lt;br /&gt;var contentRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();&lt;br /&gt;var fetchedContent = new Dictionary&amp;lt;ContentInLanguageReference, IContent&amp;gt;();&lt;br /&gt;foreach (var language in searchResult.GroupBy(x =&amp;gt; x.Language))&lt;br /&gt;{&lt;br /&gt;foreach (var content in contentRepository.GetItems(language.Select(x =&amp;gt; x.ContentLink).ToList(), new LanguageSelector(language.Key)))&lt;br /&gt;{&lt;br /&gt;fetchedContent[new ContentInLanguageReference(content)] = content;&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var sortedContent = new List&amp;lt;TContentData&amp;gt;();&lt;br /&gt;foreach (var reference in searchResult)&lt;br /&gt;{&lt;br /&gt;IContent content;&lt;br /&gt;if (fetchedContent.TryGetValue(reference, out content) &amp;amp;&amp;amp; content is TContentData)&lt;br /&gt;{&lt;br /&gt;sortedContent.Add((TContentData)content);&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;{&lt;br /&gt;log.WarnFormat(&quot;Search results contain reference to a content with reference \&quot;{0}\&quot; in language \&quot;{1}\&quot; but no such content could been found.&quot;, reference.ContentLink, reference.Language);&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;return new ContentResult&amp;lt;TContentData&amp;gt;(sortedContent, searchResult);&lt;/pre&gt;</id><updated>2019-01-25T17:48:49.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>