Fabricio Rodriguez
Fabricio Rodriguez

Reputation: 4239

Multiple MemoryCache in ASp .Net Core Web API

I have an ASP .Net Core 2.2. Web API. I'd like to speed up performance by using MemoryCache. However, I need to cache 2 different types, both which use integer keys. The one type is a list of users and the other is a list of groups.

Now, I'm adding the MemoryCache service in the Startup.cs file:

services.AddMemoryCache();

and then I'm using dependency injection to access this cache in two different places (in Middleware and in a service I wrote).

From what I understand, both these caches are the same instance. So when I add my various users and groups to it, since they both have integer keys, there will be conflicts. How can I handle this? I thought about using two caches - one for each type - but (a) I'm not sure how to do this and (b) I've read somewhere that it's not recommended to use multiple caches. Any ideas?

Upvotes: 4

Views: 2981

Answers (2)

GLJ
GLJ

Reputation: 1144

I ran into this problem myself. One solution I thought of was to just two instantiate separate memory caches in a wrapper class and register the wrapper class as a singleton instance. However, this only makes sense if you have different requirements for each memory cache and/or you expect to store a massive amount of data for each memory cache (at that point, an in-memory cache may not be what you want).

Here is some example classes I want to cache.

    // If using a record, GetHashCode is already implemented through each member already
    public record Person(string Name);

    // If using a class, ensure that Equals/GetHashCode is overridden
    public class Car
    {
        public string Model { get; }
        public Car(string model)
        {
            Model = model;
        }

        public override bool Equals(object? obj)
        {
            return obj is Car car &&
                   Model == car.Model;
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(Model);
        }
    }

Here is a dual MemoryCache implementation.

    public class CustomCache : ICustomCache // Expose what you need and register it as singleton instance
    {
        private readonly MemoryCache personCache;
        private readonly MemoryCache carCache;

        public CustomCache(IOptions<MemoryCacheOptions> personCacheOptions, IOptions<MemoryCacheOptions> carCacheOptions)
        {
            personCache = new MemoryCache(personCacheOptions);
            carCache = new MemoryCache(carCacheOptions);
        }

        public void CreatePersonEntry(Person person)
        {
            _ = personCache.Set(person, person, TimeSpan.FromHours(1));
        }

        public void CreateCarEntry(Car car)
        {
            _ = carCache.Set(car, car, TimeSpan.FromHours(12));
        }
    }

If you don't have the above requirements, then you could just do what juunas mentioned and create an easy wrapper with a composite key. You still need to ensure GetHashCode is properly implemented for each class you want to store. Here, my composite key is just an integer (I used prime numbers, no specific reason) paired with an object. I didn't use a struct for the key as the MemoryCache uses a Dictionary<object, CacheEntry>, so I don't want to box/unbox the key.

    public class CustomCache : ICustomCache // Expose what you need
    {
        private readonly IMemoryCache cache;

        public CustomCache(IMemoryCache cache)
        {
            this.cache = cache;
        }

        public void CreatePersonEntry(Person person)
        {
            _ = cache.Set(CustomKey.Person(person), person, TimeSpan.FromHours(1));
        }

        public void CreateCarEntry(Car car)
        {
            _ = cache.Set(CustomKey.Car(car), car, TimeSpan.FromHours(12));
        }

        private record CompositeKey(int Key, object Value)
        {
            public static CustomKey Person(Person value) => new(PERSON_KEY, value);
            public static CustomKey Car(Car value) => new(CAR_KEY, value);

            private const int PERSON_KEY = 1123322689;
            private const int CAR_KEY = 262376431;
        }
    }

Let me know if you see anything wrong, or if there is a better solution.

Upvotes: 1

Robert Perry
Robert Perry

Reputation: 1956

Yeah, I've had the same issue before and resorted to creating an extended version of the MemoryCache that allows me to plug in different "stores".. You can do it simply by wrapping the data you're sticking into the cache in a "metadata" type class. I suppose similar to how the ServiceDescriptors wrap your service registrations in the DI?

Also, in specific answer to the point "I thought about using two caches - one for each type". This is where the problem will arise because I believe IMemoryCache gets registered as a singleton by default

Upvotes: 3

Related Questions