Caching issues in nopCommerce 4.7

1 week ago
Hi nopCommerce team,

First congratulations for the nopCommerce 4.7 release!

We are on the final testing stages before releasing all our products for nopCommerce 4.7 but while testing we noticed an issue with the caching that affects all our products.

We do try to have as much caching as possible so that there are no unnecessary trips to the database so we have some cache keys that depend on several entities.
So we use the entity cache prefixes in order to invalidate any such cache keys.
For example in our HTML Widgets plugins we depend on Condition, CustomerOverride, ProductOverride etc. entities and we build our cache keys like this:

 public static CacheKey ProductHtmlWidgetsForWidgetCacheKey => 
            new CacheKey("SevenSpikes.Nop.Plugins.HtmlWidgets." + "{0}-{1}-{2}-{3}-{4}-{5}-{6}-{7}-{8}",
                NopEntityCacheDefaults<EntityWidgetMapping>.AllPrefix,
                NopEntityCacheDefaults<Condition>.AllPrefix,
                NopEntityCacheDefaults<ConditionGroup>.AllPrefix,
                NopEntityCacheDefaults<ConditionStatement>.AllPrefix,
                NopEntityCacheDefaults<EntityCondition>.AllPrefix,
                NopEntityCacheDefaults<CustomerOverride>.AllPrefix,
                NopEntityCacheDefaults<ProductOverride>.AllPrefix);


This code has been working fine since nopCommerce 4.4 but now in 4.7 it does not seem to be working and this cache key is not invalidated/cleared when any of these prefixes have been cleared i.e when a condition entity has been deleted and the NopEntityCacheDefaults<Condition>.AllPrefix cache has been cleared by its EventCacheConsumer<Condition>.

We noticed that the CacheManager has been refactored and probably during that refactoring something has been changed and this is no longer working.

We would appreciated if you can look at it and fix it!

Thanks,
Nop-Templates.com team
1 week ago
Hi. I've analyzed your code snippet.

It worked in nopCommerce 4.60 without condition. But it worked only for in-memory caches. And it wouldn't work for Redis or MsSql cache as you expect  because this behavior did not correspond to the "concept of a prefix".

The thing is that the memory cache did not perform an actual search by prefix for nopCommerce 4.60 (and below). It created a CancellationChangeToken for each prefix, which was tied to all entities in the ICacheKeyManager (for which it was specified). When deleting by prefix, the "Cancel" method was invoked to this token, and that led to the removal of keys from the memory cache.

In nopCommerce 4.70 we standardized the approach for all types of cache. And now the prefixes are indeed prefixes (even for the memory cache). So you can override MemoryCacheManager and return the old behavior by overriding the PrepareEntryOptions and RemoveByPrefixAsync methods. But the problem with distributed caching will remain since there is no such option. For example, here is the code for the MsSql cache from version 4.60 (for 4.70 it is the same)

public override async Task RemoveByPrefixAsync(string prefix, params object[] prefixParameters)
{
  prefix = PrepareKeyPrefix(prefix, prefixParameters);

  var command =
    new SqlCommand(
      $"DELETE FROM {_distributedCacheConfig.SchemaName}.{_distributedCacheConfig.TableName} WHERE Id LIKE @Prefix + '%'");

  await PerformActionAsync(command, new SqlParameter("Prefix", SqlDbType.NVarChar) { Value = prefix });

  await RemoveByPrefixInstanceDataAsync(prefix);
}


You can see that the key “key_1-1-2-3” cannot be deleted using the prefix “Nop.Customer.all.” in any way.

I also just now realized that the CacheKey.Prefixes property is no longer needed since it is not used so it will be removed in the next major release, and accordingly, the parameter from the constructor will also go away.