I can find 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?
Edit:
How to clear MemoryCache? Dispose method provided in the link gives me an exception in asp.net 5. ObjectDisposedException: Cannot access a disposed object. Object name: 'Microsoft.Extensions.Caching.Memory.MemoryCache'.
ObjectDisposedException: Cannot access a disposed object. Object name: 'Microsoft.Extensions.Caching.Memory.MemoryCache'.
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();
}
}
The easiest way is Compact(1.0)
if it's available. This 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);
}
This code can help if you're using the standard MemoryCache. Documentation: https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1#memorycachecompact
_cache.Compact(1.0);
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();
});
}
}
}
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:
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.
my solution was to set new expiration date to all the items in cache to 1 millisecond. Then they expired and hence cache flush.
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 IDictionary<string, HashSet<object>> _keyDictionary;
public FlushableMemoryCache(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
_keyDictionary = new Dictionary<string, HashSet<object>>();
}
public void Set<T>(string cacheId, object key, T value)
{
_memoryCache.Set(key, value);
if (_keyDictionary.ContainsKey(cacheId))
{
if (!_keyDictionary[cacheId].Contains(key))
{
_keyDictionary[cacheId].Add(key);
}
}
else
{
_keyDictionary.Add(cacheId, new HashSet<object>(new[] { key }));
}
}
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!
IMemoryCache _MemoryCache;
public CacheManager(IMemoryCache MemoryCache)
{
_MemoryCache = MemoryCache;
}
public void Clear()
{
_MemoryCache.Dispose();
_MemoryCache = new MemoryCache(new MemoryCacheOptions());
}
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.