52

There is a Remove method to remove an object from IMemoryCache by its key. Is there a way to reset the whole cache and remove all objects?

Using the Dispose method as stated by How to clear MemoryCache? does not work:

ObjectDisposedException: Cannot access a disposed object.
 Object name: 'Microsoft.Extensions.Caching.Memory.MemoryCache'.
janw
  • 8,758
  • 11
  • 40
  • 62
eadam
  • 23,151
  • 18
  • 48
  • 71
  • 1
    Have you seen this post: http://stackoverflow.com/questions/4183270/how-to-clear-the-net-4-memorycache – David Tansey Dec 22 '15 at 00:48
  • Dispose gives me an exception in asp.net 5. `ObjectDisposedException: Cannot access a disposed object. Object name: 'Microsoft.Extensions.Caching.Memory.MemoryCache'. ` – eadam Dec 22 '15 at 04:57
  • As it stands, I don't think there is a way to do this yet. See here: https://github.com/aspnet/Caching/issues/96 – Jamie Dunstan Dec 22 '15 at 11:45
  • I write [my own](https://stackoverflow.com/a/49425102/3170087) implementation which support clear. Very simple, behind it uses IMemoryCache. – Makla Mar 27 '18 at 09:31
  • IMemoryCache disappoints me every time I use it :-( – Simon_Weaver Jan 27 '23 at 01:32

10 Answers10

58

See Cache in-memory in ASP.NET Core, specifically the section on Cache dependencies.

Using a CancellationTokenSource allows multiple cache entries to be evicted as a group

This code worked for me:

public class CacheProvider 
{
    private static CancellationTokenSource _resetCacheToken = new CancellationTokenSource();
    private readonly IMemoryCache _innerCache;

    /* other methods and constructor removed for brevity */

    public T Set<T>(object key, T value) 
    {
        /* some other code removed for brevity */
        var options = new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Normal).SetAbsoluteExpiration(typeExpiration);
        options.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token));

        _innerCache.Set(CreateKey(type, key), value, options);

        return value;
    }

    public void Reset()
    {
        if (_resetCacheToken != null && !_resetCacheToken.IsCancellationRequested && _resetCacheToken.Token.CanBeCanceled)
        {
            _resetCacheToken.Cancel();
            _resetCacheToken.Dispose();
        }

        _resetCacheToken = new CancellationTokenSource();
    }
}
marty
  • 486
  • 5
  • 18
aleha_84
  • 8,309
  • 2
  • 38
  • 46
  • 7
    You have to be very careful with this implementation because it can lead to major memory leaks if you won't call Reset () so often. Here is some sample code that demos such issues in action: https://gist.github.com/tatarincev/4c942a7603a061d41deb393e0aa66545 – tatarincev Mar 01 '21 at 13:15
22

UPDATE: In .NET 7 or newer, simply call the new Clear method. You can cast your IMemoryCache as follows:

// somewhere else, maybe a class variable injected via constructor
IMemoryCache memoryCacheInterface;

// later, if you need to clear, do this
if (memoryCacheInterface is MemoryCache concreteMemoryCache)
{
    concreteMemoryCache.Clear();
}

For older .NET versions, read on...

The easiest way is Compact(1.0) if it's available. Otherwise, here is some code will clear the memory cache using an extension method (tested in unit tests and on production on .NET core 2.2 and 3.1). If Compact is not available, then fallback methods are used, starting with a public Clear method, followed by an internal Clear method. If none of those are available, an exception is thrown.

/// <summary>
/// Clear IMemoryCache
/// </summary>
/// <param name="cache">Cache</param>
/// <exception cref="InvalidOperationException">Unable to clear memory cache</exception>
/// <exception cref="ArgumentNullException">Cache is null</exception>
public static void Clear(this IMemoryCache cache)
{
    if (cache == null)
    {
        throw new ArgumentNullException("Memory cache must not be null");
    }
    else if (cache is MemoryCache memCache)
    {
        memCache.Compact(1.0);
        return;
    }
    else
    {
        MethodInfo clearMethod = cache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
        if (clearMethod != null)
        {
            clearMethod.Invoke(cache, null);
            return;
        }
        else
        {
            PropertyInfo prop = cache.GetType().GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
            if (prop != null)
            {
                object innerCache = prop.GetValue(cache);
                if (innerCache != null)
                {
                    clearMethod = innerCache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
                    if (clearMethod != null)
                    {
                        clearMethod.Invoke(innerCache, null);
                        return;
                    }
                }
            }
        }
    }

    throw new InvalidOperationException("Unable to clear memory cache instance of type " + cache.GetType().FullName);
}
jjxtra
  • 20,415
  • 16
  • 100
  • 140
  • Hello @jjextra - thanks for the solution. Would the post eviction callback be fired when the cache is cleared this way? – Anand Apr 17 '19 at 15:05
  • Haven't tested that, so cannot say, I would suggest to do a test to be sure – jjxtra Apr 17 '19 at 15:21
  • 1
    None of the methods (Compact, Clear) seem to trigger the eviction callback. – infografnet Jan 31 '21 at 20:40
  • If you need more fine grained control, you could implement IMemoryCache in your own class using ConcurrentDictionary as storage. – jjxtra Jan 31 '21 at 21:58
  • Version 7.0 of Microsoft.Extensions.Caching.Memory now contains a Clear() method on MemoryCache. – Zach W Mar 31 '23 at 15:10
  • To add on to Zach's comment, note that unfortuantely the IMemoryCache interface (that MemoryCache implements) doesn't have Clear() so you may need to cast if that's what you're using. (See Luis answer for how to do this) – Simon_Weaver May 04 '23 at 20:52
10

This code can help if you're using the standard MemoryCache. Documentation: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1#memorycachecompact

_cache.Compact(1.0);
BillRob
  • 4,659
  • 4
  • 26
  • 38
scribe
  • 163
  • 1
  • 10
6

To reset IMemoryCache objects, I created a method accessing the EntriesCollection property using GetProperty() from GetType(). With the collection in hand, I used GetMethod() of GetType() to invoke the Clear() method.

Stays like this:

public void IMemoryCacheClear(IMemoryCache memoryCache)
{
    PropertyInfo prop = memoryCache.GetType().GetProperty("EntriesCollection", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Public);
    if (prop is null)
        return;
    object innerCache = prop.GetValue(memoryCache);
    MethodInfo clearMethod = innerCache.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public);
    clearMethod.Invoke(innerCache, null);
}

Currently I migrated to .NET 7 and I need to correct the method, as the EntriesCollection no longer returns the collection data. The Clear() method is now present in the MemoryCache class as of .NET 7. So to fix it, just convert the variable to MemoryCache and call the Clear() method.

The correction is like this:

public void IMemoryCacheClear(IMemoryCache memoryCache)
{
    if (memoryCache is MemoryCache cache)
    {
        cache.Clear();    
    }
}

Hope this helps!

Luis Jorge
  • 61
  • 1
  • 1
4

My solution was to create a wrapper which re-expose existing few methods and add a missing method by replacing MemoryCache object with a brand new one. Worked just fine for me. Code is below:

public interface IMyMemoryCache : IMemoryCache
{
    void Reset();
}
public class MyMemoryCache: IMyMemoryCache
{
    IMemoryCache _memoryCache;

    public MyMemoryCache()
    {
        Reset();
    }
    public void Dispose()
    {
        _memoryCache.Dispose();
    }

    public bool TryGetValue(object key, out object value)
    {
        return _memoryCache.TryGetValue(key, out value);
    }

    public ICacheEntry CreateEntry(object key)
    {
        return _memoryCache.CreateEntry(key);
    }

    public void Remove(object key)
    {
        _memoryCache.Remove(key);
    }

    public void Reset()
    {
        var existingCache = _memoryCache;
        _memoryCache = new MemoryCache(new MemoryCacheOptions());

        // Dispose existing cache (we override) in 10 minutes
        if (existingCache != null)
        {
            System.Threading.Tasks.Task.Delay(TimeSpan.FromMinutes(10))
                .ContinueWith(t =>
                {
                    existingCache.Dispose();
                });
        }
    }
}
tgralex
  • 794
  • 4
  • 14
  • 4
    I think this code will run into concurrency issues... what if another thread is reading or writing cache values when the cache is disposed? You either need to handle exceptions due to this, or have a lock when accessing (and swapping out) the cache. – Keith Bluestone Aug 14 '20 at 13:48
  • It is as thread-safe as MemoryCache itself. Sure it could be designed as a thread-safe component. – tgralex Sep 09 '20 at 18:29
  • Can you just re-assign the _memoryCache variable or do you first have to dispose the old instance? – thomasgalliker Nov 29 '20 at 17:26
  • In general, you need to call dispose all disposable objects to avoid memory leaks. One way is to collect all cache variables and dispose them together on Dispose() Another one is to dispose old one on creating a new one immediately or after a delay – tgralex Nov 30 '20 at 14:08
  • 1
    I made a change to dispose existing cache we override in 10 minutes – tgralex Nov 30 '20 at 14:14
2

The answer as of RC1 is that you can't do it out of the box from what I've read and been told (I did read on GitHub that there maybe a way to create triggers to facilitate this that are coming).

Currently, you are provided Get, Set and Remove. I see your options as:

  1. Create a cache manager wrapper that will track all of your keys, you can then remove those items in bulk as you see fit. I'm not in love with this but it would work. Of course, if you're not the one controlling the adding there could be things in the cache you are unaware of (you could compare your count to it's count to see). If you cast IMemoryCache as MemoryCache you can get the Count property which is exposed.
  2. Fork the assembly and expose the Keys and/or add a method to remove those items. There is an underlying dictionary that holds the keys. I did this, compiled it, create a Nuget package for it and then replaced the RC1 version just to see if I could (and it worked). Not sure if this is the right way but here's the commit to my fork, I just added a read only property where I dumped the keys to an object list (the keys are stored as objects). As with past MemoryCache implementations, if you exposed the keys they could be stale after they're dumped, but if you're just using them to clear all then that shouldn't matter.

https://github.com/blakepell/Caching/commit/165ae5ec13cc51c44a6007d6b88bd9c567e1d724

I posted this issue last night trying to figure out if there's a good way to inspect what's in the cache specifically (asking why don't we have a way to). If you don't ask you'll never know if it would have mattered so I figured why not.

https://github.com/aspnet/Caching/issues/149

b.pell
  • 3,873
  • 2
  • 28
  • 39
1

my solution was to set new expiration date to all the items in cache to 1 millisecond. Then they expired and hence cache flush.

mason
  • 31,774
  • 10
  • 77
  • 121
DarthVader
  • 52,984
  • 76
  • 209
  • 300
  • This method is great for testing, where you want to test the code that is run when the item is not in the cache. You probably don't want this on LIVE, so make sure to remove it before! – Rose Oct 11 '18 at 13:49
  • 1
    yeah ok but then how do you reset it? – DarthVader Oct 12 '18 at 17:04
1

I solved it by creating a FlushableMemoryCache singleton around IMemoryCache, which tracks the keys currently stored in the cache and then can just iterate over them to flush them all:

public interface IFlushableMemoryCache
{
    void Set<T>(string cacheId, object key, T value);
    bool TryGetValue<T>(object key, out T value);
    void Remove(string cacheId, object key);
    void Flush(string cacheId);
}


public class FlushableMemoryCache : IFlushableMemoryCache
{
    private readonly IMemoryCache _memoryCache;
    private readonly ConcurrentDictionary<string, HashSet<object>> _keyDictionary;

    public FlushableMemoryCache(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
        _keyDictionary = new ConcurrentDictionary<string, HashSet<object>>();
    }


    public void Set<T>(string cacheId, object key, T value)
    {
        _memoryCache.Set(key, value);

        _keyDictionary.AddOrUpdate(cacheId, new HashSet<object>(new[] {key}),
            (id, oldVal) =>
            {
                oldVal.Add(key);
                return oldVal;
            });
    }

    public bool TryGetValue<T>(object key, out T value)
    {
        return _memoryCache.TryGetValue(key, out value);
    }

    public void Remove(string cacheId, object key)
    {
        _memoryCache.Remove(key);

        if (_keyDictionary.ContainsKey(cacheId) && _keyDictionary[cacheId].Contains(key))
        {
            _keyDictionary[cacheId].Remove(key);
        }
    }

    public void Flush(string cacheId)
    {
        foreach (var key in _keyDictionary[cacheId])
        {
            _memoryCache.Remove(key);
        }

        _keyDictionary[cacheId] = new HashSet<object>();
    }
}

The services which make use of this will need to provide a cacheId which is unique to that service. That allows the Flush to only clear keys related to the specific service, and not everything in the cache!

jb637
  • 155
  • 1
  • 10
  • 1
    `Dictionary` isn't thread-safe, so this may randomly break if several services call `Set()` at once. It may be better to use a `ConcurrentDictionary` here, or lock the dictionary object during write accesses (concurrent reading is fine). – janw Feb 19 '22 at 19:30
  • 1
    @janw good point! Updated to use `ConcurrentDictionary` – jb637 Feb 21 '22 at 12:01
  • By cautious with the `AddOrUpdate`. From the [docs](https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2.addorupdate): *"The `addValueFactory` and `updateValueFactory` delegates may be executed multiple times to verify the value was added or updated as expected. However, they are called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, `AddOrUpdate` is not atomic with regards to all other operations on the `ConcurrentDictionary` class."* Tip: the `HashSet` is not thread-safe. – Theodor Zoulias Feb 21 '22 at 13:02
1

Don't use the compact-solution - it just doesn't work.

I use IMemoryCache throughout my entire project. At a specific time I had like 42k entries. After calling Compact(1.0) there were still 14k left.

The only working way seems to be described here: How to retrieve a list of Memory Cache keys in asp.net core?

Adapted to this problem I ended up using it as follows:

public static class MemoryCacheExtensions
{
    private static readonly Func<MemoryCache, object> GetEntriesCollection = Delegate.CreateDelegate(
        typeof(Func<MemoryCache, object>),
        typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance).GetGetMethod(true),
        throwOnBindFailure: true) as Func<MemoryCache, object>;

    public static IEnumerable GetKeys(this IMemoryCache memoryCache) =>
        ((IDictionary)GetEntriesCollection((MemoryCache)memoryCache)).Keys;

    public static IEnumerable<T> GetKeys<T>(this IMemoryCache memoryCache) =>
        GetKeys(memoryCache).OfType<T>();

    public static void Clear(this IMemoryCache memoryCache) => ((IDictionary)GetEntriesCollection((MemoryCache)memoryCache)).Clear();
}

Don't forget to upvote the linked answer.

UNeverNo
  • 549
  • 3
  • 8
  • 29
-1
  IMemoryCache _MemoryCache;
    public CacheManager(IMemoryCache MemoryCache)
    {
        _MemoryCache = MemoryCache;
    }
    public void Clear()
    {
        _MemoryCache.Dispose();
        _MemoryCache = new MemoryCache(new MemoryCacheOptions());
    }