alastairtree / LazyCache

An easy to use thread safe in-memory caching service with a simple developer friendly API for c#
https://nuget.org/packages/LazyCache
MIT License
1.72k stars 159 forks source link

Get all cache keys #56

Closed Uffman closed 5 years ago

Uffman commented 6 years ago

Could you add method that retrieves all cache keys for current instance?

alastairtree commented 5 years ago

This is deliberately blocked by MS in the underlying MemoryCache object, because it usually suggests you are probably trying to do something that might be unperformant. We couldn't do it easily because the dictionary is not exposed to allow us to. See the source at https://github.com/aspnet/Extensions/blob/master/src/Caching/Memory/src/MemoryCache.cs

If you really wanted to do it you would need to re-implment IMemoryCache by wrapping MemoryCache with your own implementation that kept track of all they keys, and then pass your new cache into LazyCache at construction.

Afraid I am going to follow Microsofts lead an not build this behaviour in and leave to those who need the feature to build it themselves.

JMan7777 commented 4 years ago

Hi,

Here is a dirty solution I use to get all the keys.

Beside your usual IAppCache object injection you also need to inject an IMemoryCache object. This is the underlying MS MemoryCache object. Then you can get the keys via reflection.

Hope it helps.

` //------------------------------------------------------------------- // Helper class (Found it somewhere but I forgot where ;) //-------------------------------------------------------------------

public static class PropertyHelper
{
    /// <summary>
    /// Returns a _private_ Property Value from a given Object. Uses Reflection.
    /// Throws a ArgumentOutOfRangeException if the Property is not found.
    /// </summary>
    /// <typeparam name="T">Type of the Property</typeparam>
    /// <param name="obj">Object from where the Property Value is returned</param>
    /// <param name="propName">Propertyname as string.</param>
    /// <returns>PropertyValue</returns>
    public static T GetPrivatePropertyValue<T>(this object obje, string propName)
    {
        if (obje == null) throw new ArgumentNullException(nameof(obje));
        PropertyInfo pi = obje.GetType().GetProperty(propName,
                                                    BindingFlags.Public | BindingFlags.NonPublic |
                                                    BindingFlags.Instance);
        if (pi == null)
            throw new ArgumentOutOfRangeException(nameof(propName),
                                                  string.Format(CultureInfo.DefaultThreadCurrentCulture, "Property {0} was not found in Type {1}", propName,
                                                                obje.GetType().FullName));
        return (T)pi.GetValue(obje, null);
    }

    /// <summary>
    /// Returns a private Field Value from a given Object. Uses Reflection.
    /// Throws a ArgumentOutOfRangeException if the Property is not found.
    /// </summary>
    /// <typeparam name="T">Type of the Field</typeparam>
    /// <param name="obj">Object from where the Field Value is returned</param>
    /// <param name="propName">Field Name as string.</param>
    /// <returns>FieldValue</returns>
    public static T GetPrivateFieldValue<T>(this object obje, string propName)
    {
        if (obje == null) throw new ArgumentNullException(nameof(obje));
        Type t = obje.GetType();
        FieldInfo fi = null;
        while (fi == null && t != null)
        {
            fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            t = t.BaseType;
        }
        if (fi == null)
            throw new ArgumentOutOfRangeException(nameof(propName),
                                                  string.Format(CultureInfo.DefaultThreadCurrentCulture, "Field {0} was not found in Type {1}", propName,
                                                                obje.GetType().FullName));
        return (T)fi.GetValue(obje);
    }

    /// <summary>
    /// Sets a _private_ Property Value from a given Object. Uses Reflection.
    /// Throws a ArgumentOutOfRangeException if the Property is not found.
    /// </summary>
    /// <typeparam name="T">Type of the Property</typeparam>
    /// <param name="obj">Object from where the Property Value is set</param>
    /// <param name="propName">Propertyname as string.</param>
    /// <param name="val">Value to set.</param>
    /// <returns>PropertyValue</returns>
    public static void SetPrivatePropertyValue<T>(this object obje, string propName, T val)
    {
        Type t = obje.GetType();
        if (t.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null)
            throw new ArgumentOutOfRangeException(nameof(propName),
                                                  string.Format(CultureInfo.DefaultThreadCurrentCulture, "Property {0} was not found in Type {1}", propName,
                                                                obje.GetType().FullName));
        t.InvokeMember(propName,
                       BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty |
                       BindingFlags.Instance, null, obje, new object[] { val }, CultureInfo.DefaultThreadCurrentCulture);
    }

    /// <summary>
    /// Set a private Field Value on a given Object. Uses Reflection.
    /// </summary>
    /// <typeparam name="T">Type of the Field</typeparam>
    /// <param name="obj">Object from where the Property Value is returned</param>
    /// <param name="propName">Field name as string.</param>
    /// <param name="val">the value to set</param>
    /// <exception cref="ArgumentOutOfRangeException">if the Property is not found</exception>
    public static void SetPrivateFieldValue<T>(this object obje, string propName, T val)
    {
        if (obje == null) throw new ArgumentNullException(nameof(obje));
        Type t = obje.GetType();
        FieldInfo fi = null;
        while (fi == null && t != null)
        {
            fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            t = t.BaseType;
        }
        if (fi == null)
            throw new ArgumentOutOfRangeException(nameof(propName),
                                                  string.Format(CultureInfo.DefaultThreadCurrentCulture, "Field {0} was not found in Type {1}", propName,
                                                                obje.GetType().FullName));
        fi.SetValue(obje, val);
    }
}

//------------------------------------------------------------------- // How to get the cache keys in your code //-------------------------------------------------------------------

    private List<string> GetMemoryCacheKeys()
    {
        var collection = PropertyHelper.GetPrivatePropertyValue<ICollection>(InjectedIMemoryCacheObject, "EntriesCollection");
        var keys = new List<string>();
        if (collection != null && collection.Count > 0)
        {
            foreach (var item in collection)
            {
                var methodInfo = item.GetType().GetProperty("Key");
                var val = methodInfo.GetValue(item);
                keys.Add(val.ToString());
            }
        }
        return keys;
    }`
hitmanpc commented 3 years ago

If needed I ported a dll library over to .netstandard 2.0 (where lazy cache is utilized), but needed the keys within .net framework 4.7.2 web application.

within the .netstandard 2.0:

public static IEnumerable<ICacheEntry> GetAllCacheEntries()
        {

            var field = typeof(MemoryCacheProvider).GetField("cache", BindingFlags.NonPublic | BindingFlags.Instance);
            IMemoryCache cacheObj = field.GetValue(_Cache.CacheProvider) as MemoryCache;

            var cacheProperty = typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance);
            var cacheCollection = (ICollection)cacheProperty.GetValue(cacheObj, null);

            foreach (var cacheItem in cacheCollection)
            {
                var valProp = cacheItem.GetType().GetProperty("Value");
                ICacheEntry cacheValue = (ICacheEntry)valProp.GetValue(cacheItem, null);

                yield return cacheValue;
            };

        }

Then call the GetAllCacheEntries method within your .net standard library and loop through as needed.

--edit formatting for code (learning code comments)