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

8
  • 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. Commented Apr 28, 2017 at 7: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. Commented Apr 28, 2017 at 7:13
  • I wonder why you ever would want to iterate over all items in whole cache? Commented Apr 28, 2017 at 7: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. Commented Apr 28, 2017 at 7:30
  • 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. Commented Apr 28, 2017 at 7:37

3 Answers 3

24

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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;
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.
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.
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
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

2 Comments

Hi @Lapenkov, clever solution! Why is _allKeys marked as static though? As this belongs to IMemoryCache - shouldn't this have the same lifetime?
@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..
1

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;

            }
        }
    

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.