9

On first look this looks duplicate of this question, but I am not asking how to clear cache for EF.

How can I clear entire cache set by IMemoryCache interface?

    public CacheService(IMemoryCache memoryCache) 
    {
        this._memoryCache = memoryCache;
    }

    public async Task<List<string>> GetCacheItem()
    {
        if (!this._memoryCache.TryGetValue("Something", out List<string> list))
        {
            list= await this ...

            this._memoryCache.Set("Something", list, new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.NeverRemove));
        }

        return list;
    }

This is just an example. I have many classes/methods that are storing values to cache. Now I need to remove them all.

My keys are, in some cases, created dynamically, so I don't know which keys I need to remove. Clear would be perfect.

I could write my own interface and class which would internally use IMemoryCache, but this seems overkill. Is there any easier options?

Makla
  • 9,899
  • 16
  • 72
  • 142
  • 1
    You could downcast to MemoryCache and call Clear(). – Tratcher Mar 08 '18 at 15:43
  • Do as @Tratcher suggested and add an extension method targeting the `IMemoryCache` interface. – Brad Mar 08 '18 at 22:34
  • 1
    There is no clear function or extension method on MemoryCache that allows you to clear the cache. So casing to the implementation won't help either. – alsami Mar 09 '18 at 08:21

6 Answers6

12

If you cast IMemoryCache to just MemoryCache you should now have a Compact method which you can call with a parameter value of 1.0, causing the cache to clear.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.memorycache?view=dotnet-plat-ext-6.0

If you feel that is too hacky or your IMemoryCache is something else, read on...

Disposing and removing key by key is not a good idea, too many failure points. I have used this code in production and unit tests, it works well. I have yet to find a good answer as to why IMemoryCache is missing a Clear method.

PropertyInfo prop = cache.GetType().GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
object innerCache = prop.GetValue(cache);
MethodInfo clearMethod = innerCache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
clearMethod.Invoke(innerCache, null);
jjxtra
  • 20,415
  • 16
  • 100
  • 140
9

I couldn't find any good solutions so, I wrote my own.
In SamiAl90 solution (answer) I missed all properties from ICacheEntry interface.

Internally it uses IMemoryCache.
Use case is exactly the same with 2 additional features:

  1. Clear all items from the memory cache
  2. Iterate through all key/value pairs

You have to register a singleton:

serviceCollection.AddSingleton<IMyCache, MyMemoryCache>();

Use case:

public MyController(IMyCache cache)
{
    this._cache = cache;
}

[HttpPut]
public IActionResult ClearCache()
{
    this._cache.Clear();
    return new JsonResult(true);
}

[HttpGet]
public IActionResult ListCache()
{
    var result = this._cache.Select(t => new
    {
        Key = t.Key,
        Value = t.Value
    }).ToArray();
    return new JsonResult(result);
}

Source:

public interface IMyCache : IEnumerable<KeyValuePair<object, object>>, IMemoryCache
{
    /// <summary>
    /// Clears all cache entries.
    /// </summary>
    void Clear();
}

public class MyMemoryCache : IMyCache
{
    private readonly IMemoryCache _memoryCache;
    private readonly ConcurrentDictionary<object, ICacheEntry> _cacheEntries = new ConcurrentDictionary<object, ICacheEntry>();

    public MyMemoryCache(IMemoryCache memoryCache)
    {
        this._memoryCache = memoryCache;
    }

    public void Dispose()
    {
        this._memoryCache.Dispose();
    }

    private void PostEvictionCallback(object key, object value, EvictionReason reason, object state)
    {
        if (reason != EvictionReason.Replaced)
            this._cacheEntries.TryRemove(key, out var _);
    }

    /// <inheritdoc cref="IMemoryCache.TryGetValue"/>
    public bool TryGetValue(object key, out object value)
    {
        return this._memoryCache.TryGetValue(key, out value);
    }

    /// <summary>
    /// Create or overwrite an entry in the cache and add key to Dictionary.
    /// </summary>
    /// <param name="key">An object identifying the entry.</param>
    /// <returns>The newly created <see cref="T:Microsoft.Extensions.Caching.Memory.ICacheEntry" /> instance.</returns>
    public ICacheEntry CreateEntry(object key)
    {
        var entry = this._memoryCache.CreateEntry(key);
        entry.RegisterPostEvictionCallback(this.PostEvictionCallback);
        this._cacheEntries.AddOrUpdate(key, entry, (o, cacheEntry) =>
        {
            cacheEntry.Value = entry;
            return cacheEntry;
        });
        return entry;
    }

    /// <inheritdoc cref="IMemoryCache.Remove"/>
    public void Remove(object key)
    {
        this._memoryCache.Remove(key);
    }

    /// <inheritdoc cref="IMyCache.Clear"/>
    public void Clear()
    {
        foreach (var cacheEntry in this._cacheEntries.Keys.ToList())
            this._memoryCache.Remove(cacheEntry);
    }

    public IEnumerator<KeyValuePair<object, object>> GetEnumerator()
    {
        return this._cacheEntries.Select(pair => new KeyValuePair<object, object>(pair.Key, pair.Value.Value)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    /// <summary>
    /// Gets keys of all items in MemoryCache.
    /// </summary>
    public IEnumerator<object> Keys => this._cacheEntries.Keys.GetEnumerator();
}

public static class MyMemoryCacheExtensions
{
    public static T Set<T>(this IMyCache cache, object key, T value)
    {
        var entry = cache.CreateEntry(key);
        entry.Value = value;
        entry.Dispose();

        return value;
    }

    public static T Set<T>(this IMyCache cache, object key, T value, CacheItemPriority priority)
    {
        var entry = cache.CreateEntry(key);
        entry.Priority = priority;
        entry.Value = value;
        entry.Dispose();

        return value;
    }

    public static T Set<T>(this IMyCache cache, object key, T value, DateTimeOffset absoluteExpiration)
    {
        var entry = cache.CreateEntry(key);
        entry.AbsoluteExpiration = absoluteExpiration;
        entry.Value = value;
        entry.Dispose();

        return value;
    }

    public static T Set<T>(this IMyCache cache, object key, T value, TimeSpan absoluteExpirationRelativeToNow)
    {
        var entry = cache.CreateEntry(key);
        entry.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow;
        entry.Value = value;
        entry.Dispose();

        return value;
    }

    public static T Set<T>(this IMyCache cache, object key, T value, MemoryCacheEntryOptions options)
    {
        using (var entry = cache.CreateEntry(key))
        {
            if (options != null)
                entry.SetOptions(options);

            entry.Value = value;
        }

        return value;
    }

    public static TItem GetOrCreate<TItem>(this IMyCache cache, object key, Func<ICacheEntry, TItem> factory)
    {
        if (!cache.TryGetValue(key, out var result))
        {
            var entry = cache.CreateEntry(key);
            result = factory(entry);
            entry.SetValue(result);
            entry.Dispose();
        }

        return (TItem)result;
    }

    public static async Task<TItem> GetOrCreateAsync<TItem>(this IMyCache cache, object key, Func<ICacheEntry, Task<TItem>> factory)
    {
        if (!cache.TryGetValue(key, out object result))
        {
            var entry = cache.CreateEntry(key);
            result = await factory(entry);
            entry.SetValue(result);
            entry.Dispose();
        }

        return (TItem)result;
    }
}
Xcalibur37
  • 2,305
  • 1
  • 17
  • 20
Makla
  • 9,899
  • 16
  • 72
  • 142
2

This is not possible. I looked up the code on github because my initial idea was to simply dispose it, even when it would be dirty. Caching-Middleware registers a single implementation of IMemoryCache as singleton.

When you called dispose on it once, you can not access the cache functions ever again, until you restart the whole service.

So a workaround to accomplish this would be to store all keys that have been added in singleton service that you implement yourself. For instance smth like

public class MemoryCacheKeyStore : IMemoryCacheKeyStore, IDisposeable
{
   private readonly List<object> Keys = new List<object>();

   public void AddKey(object key) ...

   public object[] GetKeys() ....

   public void Dispose()
   {
      this.Keys.Clear();
      GC.SuppressFinalize(this);
   }
}

With that you could at somepoint access all keys, iterate through them and call the Remove(object key) function on the cache.

Dirty workaround, might cause some trouble but as far as I can tell this is the only way to remove all items at once without a service reboot :)

alsami
  • 8,996
  • 3
  • 25
  • 36
1

This discussion is also being done here: https://learn.microsoft.com/en-us/answers/answers/983399/view.html

I wrote an answer there and I'll transcribe it here:

using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory;
using ServiceStack;

public static class IMemoryCacheExtensions
{
    static readonly List<object> entries = new();

    /// <summary>
    /// Removes all entries, added via the "TryGetValueExtension()" method
    /// </summary>
    /// <param name="cache"></param>
    public static void Clear(this IMemoryCache cache)
    {
        for (int i = 0; i < entries.Count; i++)
        {
            cache.Remove(entries[i]);
        }
        entries.Clear();
    }

    /// <summary>
    /// Use this extension method, to be able to remove all your entries later using "Clear()" method
    /// </summary>
    /// <typeparam name="TItem"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool TryGetValueExtension<TItem>(this IMemoryCache cache, object key, out TItem value)
    {
        entries.AddIfNotExists(key);

        if (cache.TryGetValue(key, out object result))
        {
            if (result == null)
            {
                value = default;
                return true;
            }

            if (result is TItem item)
            {
                value = item;
                return true;
            }
        }

        value = default;
        return false;
    }
}
Silvair L. Soares
  • 1,018
  • 12
  • 28
-1

When using IMemoryTask it is a good idea to keep all your keys in constants to avoid the use of magic strings. Then create a string array that contains all your keys.

Clearing the cache then becomes very simple and you can do it like this:

private void ClearCache()
{
  foreach (var key in MemoryCacheConstants.AllKeys)
  {
    _memoryCache.Remove(key);
  }
}
  • The author states that some keys are dynamic so a list of keys known at build-time (that may or may not have actual cache entries) doesn't answer the question. – JRoughan Feb 20 '23 at 11:00
-2

In ASP.NET Core, you can add services.AddMemoryCache() in ConfigureServices of Startup. Once in controller, just use DI of IMemoryCache parameter like this:

NameController(IMemoryCache memoryCache)

Then, when you want remove or clear cache use

memoryCache.Remove(yourkeycache)

This works from 1.1

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 2
    This does not solve the problem of the author: `My keys are, in some cases, created dynamically, so I don't know which keys I need to remove.` – ˈvɔlə Apr 22 '20 at 00:28