Skip to main content

Outline

At a glance
  • Use this for reads: IContentLoader (fast + cache-friendly).
  • Avoid: N+1 loading patterns, “LINQ over the whole tree,” and repeated loads in the same request.
  • Language matters: use LoaderOptions + LanguageLoaderOption intentionally.
  • Reporting limit: CMS repositories can audit content + metadata, not analytics or aggregates.


1. Introduction

Efficient content retrieval is one of those “invisible” features users immediately notice when it goes wrong. Slow navigation menus, laggy landing pages, and timeouts during heavy traffic often trace back to how content is queried.

Optimizely CMS 12 provides solid core APIs for retrieving content, but you still have to use them with intent. These APIs are optimized for content access and content management workflows—not for analytics-style reporting. Knowing that boundary helps you pick the right tool and prevents your code from turning into a DIY data warehouse (which is a brave, doomed hobby).



2. Mastering read-only querying with IContentLoader

For read-only retrieval, IContentLoader is the first choice. It’s designed for fast reads and benefits from Optimizely’s caching behavior, so you avoid unnecessary database hits.

The role of IContentLoader

You typically inject IContentLoader and use it anywhere you need to read content without modifying it. Compared to “load then maybe modify,” this keeps the intent clear and reduces accidental write-path complexity.

Basic retrieval

  • Get<T>(ContentReference): type-safe retrieval; throws TypeMismatchException if the type doesn’t match.
  • Get<IContent>(ContentReference): generic retrieval when the type isn’t known.
using EPiServer.Core;
using System;

public class ReadOnlyQueryService
{
    private readonly IContentLoader _contentLoader;

    public ReadOnlyQueryService(IContentLoader contentLoader)
    {
        _contentLoader = contentLoader;
    }

    public void GetBasicContent(ContentReference contentLink)
    {
        try
        {
            PageData page = _contentLoader.Get<PageData>(contentLink);
            Console.WriteLine($"Loaded Page: {page.PageName}");

            IContent content = _contentLoader.Get<IContent>(contentLink);
            Console.WriteLine($"Loaded Generic Content: {content.Name}");
        }
        catch (ContentNotFoundException)
        {
            Console.WriteLine($"Content with ID {contentLink.ID} not found.");
        }
        catch (TypeMismatchException)
        {
            Console.WriteLine($"Content with ID {contentLink.ID} is not of the expected type.");
        }
    }
}


Hierarchical retrieval

  • GetChildren<T>(parent): direct children only (great for menus and single-level lists).
  • GetDescendants<T>(parent): recursive traversal (use sparingly; excellent for admin tools, risky for front-end hot paths).
using EPiServer.Core;
using System.Collections.Generic;
using System.Linq;

public class ReadOnlyQueryService
{
    private readonly IContentLoader _contentLoader;

    public ReadOnlyQueryService(IContentLoader contentLoader)
    {
        _contentLoader = contentLoader;
    }

    public void GetHierarchicalContent(ContentReference parentLink)
    {
        IEnumerable<PageData> children = _contentLoader.GetChildren<PageData>(parentLink);
        Console.WriteLine($"Children of {parentLink.ID}: {children.Count()} pages");

        IEnumerable<IContent> descendants = _contentLoader.GetDescendants<IContent>(parentLink);
        Console.WriteLine($"Descendants of {parentLink.ID}: {descendants.Count()} items");
    }
}


Batch retrieval

Batch loading prevents the classic N+1 problem (looping through references and calling Get repeatedly). If you already have a list of ContentReference values, prefer GetItems.

  • GetItems(references, loaderOptions): loads many items efficiently in one call.
using EPiServer.Core;
using EPiServer.DataAbstraction;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

public class ReadOnlyQueryService
{
    private readonly IContentLoader _contentLoader;

    public ReadOnlyQueryService(IContentLoader contentLoader)
    {
        _contentLoader = contentLoader;
    }

    public void GetBatchContent(IEnumerable<ContentReference> contentLinks, CultureInfo language)
    {
        LoaderOptions options = new LoaderOptions
        {
            LanguageLoaderOption.SpecificCulture(language),
            LanguageLoaderOption.FallbackWithMaster()
        };

        IEnumerable<IContent> items = _contentLoader.GetItems(contentLinks, options);
        Console.WriteLine($"Loaded {items.Count()} items in batch for language {language.Name}");
    }
}


Language-aware retrieval

On multilingual sites, “correct” content depends on language selection and fallback rules. Prefer being explicit: specify a culture and decide whether you want fallback to master.

Heads-up on language behavior
  • Using SpecificCulture without fallback can expose “missing translation” gaps (sometimes desired).
  • FallbackWithMaster improves user experience, but can hide editorial completeness issues if you don’t audit.


3. Performance considerations for content querying

Most performance issues come from “too many calls” or “too much content.” The APIs are fine—your loops are usually the villain.

Caching mechanisms

Optimizely includes caching behavior that IContentLoader benefits from automatically. That’s one reason it’s preferred for reads.

  • Built-in caching: repeated reads can be served from cache instead of the database.
  • Automatic invalidation: publishing/updating content triggers cache invalidation so you don’t serve stale versions.
  • Custom caching caution: add extra caching only when you can prove it helps and you understand invalidation implications.


Optimizing queries

  • Avoid N+1: don’t loop references and call Get repeatedly; use GetItems.
  • Minimize roundtrips: structure retrieval so each request does the fewest necessary content loads.
  • In-memory filtering is situational:
    • Small sets: load + LINQ filtering is fine (e.g., direct children).
    • Large sets: “load everything then filter” becomes expensive fast.
  • Prefer specific types: querying StandardPage (or similar) is usually cleaner than querying IContent and casting.


Lazy loading vs. eager loading

In many common cases, when you load a content item you get its properties as part of that load. That’s generally helpful because you avoid “property-by-property” query patterns. The tradeoff is memory pressure if you load too many large items in one go—so the real fix is still: load fewer items.



4. Reporting limitations of core CMS APIs

IContentLoader and IContentRepository are excellent for content retrieval and content management. They are not an analytics engine, and they don’t store behavioral metrics like pageviews or conversions.

What core APIs can do

  • Basic listings: content lists by hierarchy/type, plus simple property checks.
  • Metadata extraction: created/changed timestamps, authorship info, publish windows, version status.
  • Simple audits: “missing meta description,” “old tracking ID,” “broken references,” and similar hygiene checks.


What core APIs cannot do

  • Aggregations of user behavior: pageviews, sessions, bounce rate, conversions aren’t in the CMS database.
  • Historical trends: “month over month performance” requires analytics tooling.
  • Complex analytics: funnels, cohorts, segmentation logic, and experiment results live elsewhere.
  • Non-CMS datasets: commerce orders, CDP profiles, CRM data need their own APIs or a shared reporting store.
  • Advanced full-text search: ranking, fuzziness, faceting, and relevancy tuning belong to a search product (e.g., Find).


When to use specialized tools

If the question sounds like “search,” “insights,” “metrics,” or “cross-system reporting,” core CMS APIs aren’t the right hammer.

  • Optimizely Find: complex search, filtering, faceting, and content discovery.
  • Analytics platforms (GA4, ODP): visitor behavior, performance metrics, personalization signals.
  • Data warehouse + BI: multi-source reporting across CMS + Commerce + ODP + CRM, etc.


5. Conclusion

Fast CMS sites are usually fast because they’re disciplined: read with IContentLoader, batch where possible, avoid N+1, and don’t traverse huge trees on hot paths.

For reporting, treat CMS repositories as content tools: great for audits, metadata, and structured listings. When you need advanced search or analytics, reach for the specialized systems built for those workloads.