Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to retrieve a list of Memory Cache keys in asp.net core?

To be succinct. Is possible list all register keys from Memory Cache in the .Net Core Web Application?

I didn't find anything in IMemoryCache interface.

like image 865
Carlos Avatar asked Aug 09 '17 17:08

Carlos


4 Answers

There is no such thing in .Net Core yet. Here is my workaround:

 var field = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance);
 var collection = field.GetValue(_memoryCache) as ICollection;
 var items = new List<string>();
 if (collection != null)
 foreach (var item in collection)
 {
      var methodInfo = item.GetType().GetProperty("Key");
      var val = methodInfo.GetValue(item);
      items.Add(val.ToString());
 }
like image 160
MarkM Avatar answered Nov 01 '22 04:11

MarkM


As other answers point out, current implementation of Microsoft.Extensions.Caching.Memory.MemoryCache does not expose any members allowing to retrieve all cache keys, although there is a way around the problem if we use reflection.

This answer builds upon the one by MarkM, adds some speed to the solution by reducing reflection usage to a minimum and packs everything into a single extension class.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Caching.Memory;

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>();
}

Usage:

var cache = new MemoryCache(new MemoryCacheOptions());
cache.GetOrCreate(1, ce => "one");
cache.GetOrCreate("two", ce => "two");

foreach (var key in cache.GetKeys())
    Console.WriteLine($"Key: '{key}', Key type: '{key.GetType()}'");

foreach (var key in cache.GetKeys<string>())
    Console.WriteLine($"Key: '{key}', Key type: '{key.GetType()}'");

Output:

Key: '1', Key type: 'System.Int32'
Key: 'two', Key type: 'System.String'
Key: 'two', Key type: 'System.String'

Notes:

  • Reflection usage is reduced to a single call that builds the GetEntriesCollection delegate. When we're working with EntriesCollection's keys, reflection is not used. This can save some time and resources when we're traversing a long collection of MemoryCache's keys.
  • In the solution we are casting MemoryCache.EntriesCollection property to IDictionary despite that its backing field MemoryCache._entries is of type ConcurrentDictionary<object, CacheEntry>. We cannot cast it directly to that type because CacheEntry type is internal.
  • The above does not work in .NET 7 Preview 1 and newer: EntriesCollection property has been removed from MemoryCache and its backing field _entries has moved into a private class CoherentState within MemoryCache. I will attempt to provide a similarly performant solution for .NET 7 soon. Meanwhile feel free to add a unit test to your solutions which makes sure that MemoryCacheExtensions.GetKeys() does not throw exceptions when used.
like image 27
roxton Avatar answered Nov 01 '22 04:11

roxton


MarkM's answer didn't quite work for me, it wouldn't cast the results to an ICollection, but I took the idea and came up with this that works quite well for me. Hopefully it helps someone else out there too:

// Get the empty definition for the EntriesCollection
var cacheEntriesCollectionDefinition = typeof(MemoryCache).GetProperty("EntriesCollection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

// Populate the definition with your IMemoryCache instance.  
// It needs to be cast as a dynamic, otherwise you can't
// loop through it due to it being a collection of objects.
var cacheEntriesCollection = cacheEntriesCollectionDefinition.GetValue(instanceIMemoryCache) as dynamic;

// Define a new list we'll be adding the cache entries too
List<Microsoft.Extensions.Caching.Memory.ICacheEntry> cacheCollectionValues = new List<Microsoft.Extensions.Caching.Memory.ICacheEntry>();

foreach (var cacheItem in cacheEntriesCollection)
{
    // Get the "Value" from the key/value pair which contains the cache entry   
    Microsoft.Extensions.Caching.Memory.ICacheEntry cacheItemValue = cacheItem.GetType().GetProperty("Value").GetValue(cacheItem, null);

    // Add the cache entry to the list
    cacheCollectionValues.Add(cacheItemValue);
}

// You can now loop through the cacheCollectionValues list created above however you like.
like image 23
dislexic Avatar answered Nov 01 '22 04:11

dislexic


Currently there is no such method in the IMemoryCache interface to return all the cache keys. As per this github issue comments, i do not think that would be added in the future.

Quoting Eilons comment

I think it's doubtful this would be available because part of the idea with caching is that mere moments after you ask it a question, the answer could have changed. That is, suppose you have the answer to which keys are there - a moment later the cache is purged and the list of keys you have is invalid.

If you need the keys, you should maintain the list of keys in your app while you set items to the cache and use that as needed.

Here is another useful github issue

Will there be GetEnumerator() for MemoryCache ?

like image 8
Shyju Avatar answered Nov 01 '22 04:11

Shyju