Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IMemoryCache Guaranteed Unique new Keys .NET-Core

Tags:

I'm attempting to use the Microsoft.Extensions.Caching.Memory.IMemoryCache interface / class.

I need to add a new item to the cache, and make sure I don't override anything else that's already saved. At the moment, all keys are automatically generated and randomized (not sequential).

How can I test a random key for uniqueness against my current cache items?

Or, How can I get a guaranteed unique key? It'd be ok getting an auto-generated key that can be used for later retrieval.

https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory has a few examples but using the .TryGetValue(object key, object out value) for each key I generate to test uniqueness seems like overkill, and when considering multi-threaded environments this could be a problem.

The blog https://wildermuth.com/2016/04/14/Using-Cache-in-ASP-NET-Core-1-0-RC1 uses the same pattern of TryGetValue(key, out value) for deliberate Keys.

I'm hoping NOT to have to keep this list of generated keys somewhere else, i.e. another list separated list, as this would take me back to the issue of trimming the list when it grew old.

Just as an extra, in this specific case I'm be passing the keys around as url-querystring parameters, so url-compatible strings would be super. Thanks in advance.

like image 436
Guid Avatar asked Feb 26 '17 00:02

Guid


1 Answers

Using a GUID for a cache key is not a good solution, as you have already discovered. The main problem is that after the GUID is generated, there is no way to reliably regenerate it into the same key in order to get the data out of the cache.


Usually, when I create a cache the keys are based off of the entity being cached or the method that is caching it. But it is also possible to make the cache based on a combination of values that together make the value unique.

Example Using Entity

public class Employee
{
    int Id { get; set; }
    string Name { get; set; }
}

To get the key from an entity, we simply use a constant value in addition to the primary key of the entity.

private const string KEY_PREFIX = "Employee_";
private object syncLock = new object();

// innerEmployeeRetriever and cache are populated through the constructor
public Employee GetEmployee(int id)
{
    string key = KEY_PREFIX + id.ToString();

    // Get the employee from the cache
    var employee = cache[key];
    if (employee == null)
    {
        lock (syncLock)
        {
            // Double-check that another thread didn't beat us
            // to populating the cache
            var employee = cache[key];
            if (employee == null)
            {
                employee = innerEmployeeRetriever.GetEmployee(id);
                cache[key] = employee;
            }
        }
    }
    return employee;
}

Example Using Method Name

private object syncLock = new object();

// innerEmployeeRetriever and cache are populated through the constructor
public Employee GetEmployeeList()
{
    string key = "GetEmployeeList";

    // Get the employee from the cache
    var employees = cache[key];
    if (employees == null)
    {
        lock (syncLock)
        {
            // Double-check that another thread didn't beat us
            // to populating the cache
            var employees = cache[key];
            if (employees == null)
            {
                employees = innerEmployeeRetriever.GetEmployeeList();
                cache[key] = employees;
            }
        }
    }
    return employees;
}

Example Using a Combination of Values

You can also build the keys from several different values that in combination make the entity unique. This is helpful if you don't have a primary key to work with or you have several different contexts that you want to cache separately. This example was taken from MvcSiteMapProvider:

protected string GetCacheKey(string memberName)
{
    // NOTE: We must include IsReadOnly in the request cache key 
    // because we may have a different 
    // result when the sitemap is being constructed than when 
    // it is being read by the presentation layer.
    return "__MVCSITEMAPNODE_" + this.SiteMap.CacheKey + "_" + this.Key 
        + "_" + memberName + "_" + this.IsReadOnly.ToString() + "_";
}

In this case, we are building the key based on the unique key of the parent SiteMap the node belongs to, the unique key of the node, the method or property name, and whether or not the readonly flag is currently set. Each unique set of these values results in a separate cache key, making a separate cache for each combination.

Of course, for this to work right, there should be some kind of "safe" delimiter between the values to prevent the same key from being created by concatenating different values. For example, "1" + "23" is the same string as "12" + "3", but you can prevent these sort of collisions by using an underscore, pipe character, comma, or some other delimiter that won't be in the data itself to separate the values ("1" + "_" + "23" is not the same string as "12" + "_" + "3").


The bottom line is that the cache key must somehow represent what is in the cache in order for it to be useful. Your application should understand how to provide the data that makes up the key so it can be re-made if the same data needs to be retrieved again.

like image 83
NightOwl888 Avatar answered Oct 11 '22 14:10

NightOwl888