4

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?

2
  • 3
    Use a composite key? E.g. "user_1", "group_1". Commented Feb 5, 2019 at 9:39
  • Thanks juunas. Yes, that's indeed a good suggestion! Commented Feb 5, 2019 at 9:50

2 Answers 2

3

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

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

1 Comment

Thanks Robert. That's also a very good suggestion. For now, I went for the easier solution proposed juunas, but I might very well switch to your method. I must say yours sounds like the more "elegant" solution...
1

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.

2 Comments

I have a question - would you still register by doing app.UseInMemoryCache()? I wondered if registering a singleton with 2 memory caches would do all the required setup to activate it? Though thinking about it, I have seen some examples with other bits of setup where you need to "turn it on" by registering and then you can use the MemoryCache, so I'm thinking not..
I'd have to revisit this again, but I believe for "CustomCache" with the single constructor argument, you need to use app.UseInMemoryCache(). The CustomCache with two constructor arguments you likely need to register as a separate singleton. @orgg

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.