9

There's no available method in IMemoryCache that allows to iterate through each cached item. My project is small, I don't want to use other options like Redis.

namepsace    Microsoft.Extensions.Caching.Memory{
        public static class CacheExtensions
    {
        public static object Get(this IMemoryCache cache, object key);
        public static TItem Get<TItem>(this IMemoryCache cache, object key);
        public static TItem GetOrCreate<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, TItem> factory);
        [AsyncStateMachine(typeof(CacheExtensions.<GetOrCreateAsync>d__9<>))]
        public static Task<TItem> GetOrCreateAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factory);
        public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value);
        public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, DateTimeOffset absoluteExpiration);
        public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, TimeSpan absoluteExpirationRelativeToNow);
        public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, IChangeToken expirationToken);
        public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options);
        public static bool TryGetValue<TItem>(this IMemoryCache cache, object key, out TItem value);
    }
}

https://github.com/aspnet/Caching/blob/dev/src/Microsoft.Extensions.Caching.Memory/MemoryCache.cs

Tseng
  • 61,549
  • 15
  • 193
  • 205
nam vo
  • 3,271
  • 12
  • 49
  • 76
  • 2
    I don't think there is such functionality, easy thing you can do is to wrap the memory cache in a new class that stores cached keys (pre-store + new entry on each item set) and a method that iterates over them calling the Get method from the MemoryCache. – Samuil Petrov Apr 28 '17 at 07:00
  • 2
    Iterating a MemoryCache is simply not a use case for that class. Sure, you can add that by wrapping the cache in another class, but then you have to deal with all the problems that concurrent access to the cache causes. – Dirk Apr 28 '17 at 07:13
  • I wonder why you ever would want to iterate over all items in whole cache? – Evk Apr 28 '17 at 07:16
  • @Evk say I have some cached items with key patterns: "abc.xyz-{0}" => for a single item, "abc.xyz" => for all items. Now if I make a change to item data like changing the name..., I want to remove all cached items with abc.xy using Regex or whatever to make sure the next request will get a refresh updated data. – nam vo Apr 28 '17 at 07:30
  • Thanks all, I'll try to do a wrapper class then. – nam vo Apr 28 '17 at 07:31
  • Well memory cache is just too basic for this use case, you will have to implement such logic yourself (as mentioned) or use more sophisticated alternative. – Evk Apr 28 '17 at 07:32
  • 2
    @Evk well, The Memory cache in MVC 5, you can write something like this. System.Runtime.Caching.MemoryCache.Default.Select(p=> p.Key) I understand .net core keeps it simple at first. – nam vo Apr 28 '17 at 07:37
  • Does this answer your question? [How to retrieve a list of Memory Cache keys in asp.net core?](https://stackoverflow.com/questions/45597057/how-to-retrieve-a-list-of-memory-cache-keys-in-asp-net-core) – Michael Freidgeim Nov 20 '20 at 04:55

3 Answers3

25

You should cache two type of items.

  1. You cache your properties as they are, abc.xyz-{0}.
  2. Second cache a list of property under the main key name, abc.xyz

Sample Code:

cache.Set("abc.xyz-name", name, TimeSpan.FromMinutes(30));
cache.Set("abc.xyz-lastname", lastname, TimeSpan.FromMinutes(30));
cache.Set("abc.xyz-birthday", birthday, TimeSpan.FromMinutes(30));
cache.Set("abc.xyz", new List<string> { "abc.xyz-name", "abc.xyz-lastname", "abc.xyz-birthday" }, TimeSpan.FromMinutes(30));

and when deleting:

var keys = cache.Get<List<string>>("abc.xyz");
foreach(var key in keys)
    cache.Remove(key);
cache.remove("abc.xyz");

Most of the services use IDistributedCache (in your case MemoryDistributedCache when registered - which again injects IMemoryCache which is MemoryCache class).

In a distributed cache you can't iterate over all keys as there are potentially millions of keys and this would significantly reduce the performance of the cached service if you could/would iterate over it.

So the above solution is also friendly and ready for the case when you replace your memory cache with a distributed cache, such as Redis.

Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 4
    i want to note that such solution is not concurrency-proof unless there is some sort of concurrency control implemented; consider two independent execution flows adding a new property under the same bucket -- it might lead to cases where property is added to the cache, however the bucket misses it; – Eugene D. Gubenkov Apr 09 '20 at 08:01
  • 1
    I think simply replacing your MemoryCache with a Redis cache is a pipedream and I don't get why they would ever need to share interfaces. For one you definitly don't want to do your distributed cache with synchronous methods. Second you need to take into account that you have a connection problem with your distributed cache. – Dirk Boer May 14 '20 at 10:12
  • No offense to the rest of your answer btw - just criticism to the .NET Core Team for not exposing the Keys property on the MemoryCache. This means everyone has to roll out their own workarounds, including dealing with complicated concurrency issues. – Dirk Boer May 14 '20 at 10:13
  • 1
    Well thing is, adding it to the interface would force every implementation to implement the iteration even stores which don't support it. Plus its bad for performance, you need to lock the keys during iteration (because they could expire while iterating) this then becoming the bottle neck of a component thats supposed to speed up – Tseng May 16 '20 at 03:54
6

For large project this is a bit an awkward way. I'd recommend you to use a wrapper class MemoryCacheManager and register it as Singleton. https://gist.github.com/vlapenkov/0a66c40221f9c56d12eb0420fb7cef77

use

_manager.Get("key", ()=>"value") // to set a key,

_manager.GetKeys() // get all keys

_manager.Remove("key") //remove one key

_manager.Clear() //remove all keys
Lapenkov Vladimir
  • 3,066
  • 5
  • 26
  • 37
  • Hi @Lapenkov, clever solution! Why is `_allKeys` marked as `static` though? As this belongs to IMemoryCache - shouldn't this have the same lifetime? – Dirk Boer May 13 '20 at 11:23
  • @Lapenkov - I think this solution will work on a single server since the "_allKeys" dictionary methioned in the solution on the link above will be maintained per machine - or maybe there is something I am missing.. – fdhsdrdark Jun 30 '20 at 06:22
0

You could use private properties and reflection to gain access (with the risks it entails). This example assumes your cache instance is stored in 'appGlobals.memoryCache'

 var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
 var cacheEntriesCollection = cacheEntriesCollectionDefinition?.GetValue(appGlobals.memoryCache) as dynamic;

        if (cacheEntriesCollection != null)
        {
            foreach (var cacheItem in cacheEntriesCollection)
            {
                var cacheKey = cacheItem.GetType().GetProperty("Key").GetValue(cacheItem) as string;

            }
        }
    
Alon S
  • 107
  • 13