Try our conversational search powered by Generative AI!

Caching in content provider with dependency to "local" content

Vote:
 

I've implemented a content provider that clones a structure of content to another part of the structure. The code is based on ClonedContentProvider in Alloy but with some minor changes.

When an editor adds new content in the structure that is being cloned it doesn't show up in the cloned structure. However, if you edit any of the children an publish, then the new content shows up. This behavior is reproducible in Alloy.

I've tried adding cache dependencies in all kind of ways in the two following overridden methods (copied from the ClonedContentProvider linked above):

        protected override void SetCacheSettings(IContent content, CacheSettings cacheSettings)
        {
            // Make the cache of this content provider depend on the original content
            cacheSettings.CacheKeys.Add(DependencyHelper.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(content.ContentLink.ID)));
        }

        protected override void SetCacheSettings(ContentReference contentReference, IEnumerable children, CacheSettings cacheSettings)
        {
            // Make the cache of this content provider depend on the original content

            cacheSettings.CacheKeys.Add(DependencyHelper.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(contentReference.ID)));

            foreach (var child in children)
            {
                cacheSettings.CacheKeys.Add(DependencyHelper.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(child.ContentLink.ID)));
            }
        }

By the way, shouldn't these keys be added to MasterKeys and not CacheKeys?

I've tried adding keys with DependencyHelper.ContentCacheKeyCreator.CreateChildrenCacheKey and whatnot. But with no luck.

Does anybody know how to solve this and set the correct cache dependencies? Maybe someone with insight in the inner workings of content providers and how the cache is handled for listings and loading of content.

I also suspect there is a bug in above methods, they don't check whether the passed in content or content reference is the "entry root" of the content provider. Because when it is, we should use the "clone root" instead to generate the correct cache keys. 

Is result from EPiServer.DataAbstraction.ContentStore cached? Because if it is I guess I can turn off caching in my content provider instead?

#191920
May 02, 2018 19:35
Vote:
 

I have not tried to reproduce the issue yet (I can try when I get time) but in short terms what gets evicted when a new content is created is the children listing of the parent. So theoretically it should be enough if you in the SetCacheSettings that takes a children listing as a parameter adds a dependency to 

DependencyHelper.ContentCacheKeyCreator.CreateChildrenCacheKey(contentReference).

But as I understood you this was something you already tried? I will see if I can reproduce it.

And no the result of ContentStore is not cached

#191931
May 03, 2018 8:26
Vote:
 

To clarify contentReference that is passed to DependencyHelper.ContentCacheKeyCreator.CreateChildrenCacheKey(contentReference) should be the reference to the node that you clone, not the node in your cloned structure,.

#191933
May 03, 2018 8:28
Vote:
 

Hmmm I DID try that, but then I recieved an error telling me that the object returned from the cache was of the wrong type. Which led me to think that the code in Alloy is actually wrong. We shouldn't add the cache keys to cacheSettings.CacheKeys but rather cacheSettings.MasterKeys. So I tried that with no luck. But now when I'm trying again it's working. Very weird. 

This is the code I ended up with now, and it seems to work:

protected override void SetCacheSettings(ContentReference contentReference, IEnumerable<GetChildrenReferenceResult> children, CacheSettings cacheSettings)
{
    // If retrieving children for the entry point, we retrieve pages from the clone root, so make
    // sure we're working agains the "correct" content reference.
    var originalContentReference = contentReference.CompareToIgnoreWorkID(this.EntryPoint) ? this.EndPoint : contentReference;

    var originalContent = this.ContentLoader.Get<IContent>(new ContentReference(originalContentReference.ID));

    if (originalContent is ILocale locale)
    {
        cacheSettings.MasterKeys.Add(this.ContentCacheKeyCreator.CreateChildrenCacheKey(originalContent.ContentLink, locale.Language.Name));
    }

    // Make the cache of this content provider depend on the original content
    cacheSettings.MasterKeys.Add(this.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(originalContentReference.ID)));

    foreach (var child in children)
    {
        cacheSettings.MasterKeys.Add(this.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(child.ContentLink.ID)));
    }
}

NB that I'm setting MasterKeys and also checking whether the content reference is the entry point.

So to be clear; is it correct to set cacheSettings.MasterKeys and not cacheSettings.CacheKeys? Not sure how to check if the content gets cached correctly.

#191947
May 03, 2018 9:45
Vote:
 

When you have dependencies to other content or children listings you should use CacheKeys and not MasterKeys. Reason for that is that if you use MasterKeys then if the key is not present in cache then CMS will add a cache entry for that entry (like new object()). But then if some other code tries to read that cached item and expects it to be of a specific type like

var content = (IContent) cache.Get(key);

then you would get the error that the item is of wrong type as you saw before.

#191948
May 03, 2018 9:52
Vote:
 

MasterKeys are mainly used if you like to be able to evict alot of items at same time. Like for example all items from your content provider then you can add a dependency from all of them with a common master key and then evict all of them by removing the master key.

#191949
May 03, 2018 9:54
Vote:
 

Ok, thanks, got it. What confused me is the documentation for MasterKeys. It says:

Master keys are used as markers to set up common dependencies without having
to create the cache entries first. If you set up a master key dependency, there
is no need for the corresponding entry to exist before adding something that
depends on the master key.


So if we're not using master keys, the entry must be in the cache first. Which I guess I can't guarantee it is. But if you say so :) And it actually works when I changed to cache keys instead of master keys. Not sure why it didn't throw an error with above code then or why it actually threw an error when I used cache keys before, but it might been related to some other issue.

For reference this is my code now:

protected override void SetCacheSettings(IContent content, CacheSettings cacheSettings)
{
    // If retrieving from the entry point, we retrieving from the clone root
    var originalContentReference = content.ContentLink.Equals(this.EntryPoint, true) ? this.EndPoint : content.ContentLink;

    // Make the cache of this content provider depend on the original content
    cacheSettings.CacheKeys.Add(this.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(originalContentReference.ID)));
}

protected override void SetCacheSettings(ContentReference contentReference, IEnumerable<GetChildrenReferenceResult> children, CacheSettings cacheSettings)
{
    // If retrieving children for the entry point, we retrieve pages from the clone root, so make
    // sure we're working against the "correct" content reference.
    var originalContentReference = contentReference.Equals(this.EntryPoint, true) ? this.EndPoint : contentReference;

    var originalContent = this.ContentLoader.Get<IContent>(new ContentReference(originalContentReference.ID));

    if (originalContent is ILocale locale)
    {
        cacheSettings.CacheKeys.Add(this.ContentCacheKeyCreator.CreateChildrenCacheKey(originalContent.ContentLink, locale.Language.Name));
    }

    // Make the cache of this content provider depend on the original content
    cacheSettings.CacheKeys.Add(this.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(originalContentReference.ID)));

    foreach (var child in children)
    {
        cacheSettings.CacheKeys.Add(this.ContentCacheKeyCreator.CreateCommonCacheKey(new ContentReference(child.ContentLink.ID)));
    }
}

I will let David Knipe know so he can fix the code in Alloy Demo Kit as well. Thanks again.

#191950
Edited, May 03, 2018 10:22
Vote:
 

You are correct that the advantage of masterkeys is that they are guaranteed to exist in the cache while cachekeys are not. In your case I guess it is likely that the cloned content exist since I imagine that it gets loaded as part of the delivery from your content provider. 

If you really want to ensure that the referenced item exist in the cache then you need to check that and load it yourself. 

#191961
May 03, 2018 11:17
Vote:
 

Since the content is loaded through ContentStore in most cases, which doesn't cache anything, it's no guarantee that they exist in the cache already. But hopefully they are since IContentLoader is used as well. So potentially we might run into the issue.

#191963
May 03, 2018 11:28
Vote:
 

Ah, I'll see. What is the reason to not use IContentLoader instead of ContentStore? i am thinking that then you get the benefit of that the cloned content might already be cached.

#191964
May 03, 2018 11:38
Vote:
 

That is a good question, and maybe a question for the original author of the code. But I've made sure that my code is using IContentLoader now. I guess one reason is that ContentStore has similar methods to what the content provider requires, e.g. LoadChildrenReferencesAndTypes.

#191990
May 03, 2018 12:33
Vote:
 

I see, then a suggestion could be to use the ContentProvider directly (can be achieved from IContentProviderManager) then you would both get caching support and methods like LoadChildrenReferencesAndTypes

#191994
May 03, 2018 12:38
Vote:
 

There are some things that could be good to be aware of when calling directly against a content provider (compared to calling through IContentLoader/IContentRepository). One is that events like IContentEvents.LoadedContent will not be fired another thing is that Fallback/Replacement language functionality will not be considered. Also will access rights not be checked (not relevant for Load but if you would call Save/Delete directly against a ContentProvider).

In your case it probably does not matter but I thought I should clarify the differences.

#191995
May 03, 2018 12:46
Vote:
 

There are some things that could be good to be aware of when calling directly against a content provider (compared to calling through IContentLoader/IContentRepository). One is that events like IContentEvents.LoadedContent will not be fired another thing is that Fallback/Replacement language functionality will not be considered. Also will access rights not be checked (not relevant for Load but if you would call Save/Delete directly against a ContentProvider).

In your case it probably does not matter but I thought I should clarify the differences.

#191996
May 03, 2018 12:46
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.