Search code examples
c#asp.net-corecaching

Asp.Net Core: should I use AddScoped or AddSingleton on a cache service


In an asp.net core application I have a dependency-injected cache service, which is essentially a wrapper around the built-in MemoryCache.

Here is the example class that gets cached, it contains lists of some enums that are always used by the web application:

public class GetEnums
{
    public List<MyEnum1> Ones { get; set; }
    public List<MyEnum2> Twos { get; set; }
}

Here is an example method call on my cache service class, it simply retrieves the GetEnums class:

public class CacheService: ICacheService
{
    private IMemoryCache MemoryCache {get;set;}
    public CacheService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }
    public GetEnums GetEnums()
    {
        if (MemoryCache.TryGetValue("GetEnums", out GetEnums getEnums))
            return getEnums;

        getEnums = MyRepository.GetEnums();
        MemoryCache.Set(CacheKeys.GetEnums, getEnums, _enumMemoryCacheEntryOptions);

        return getEnums;
    }
}

In my ConfigureServices method I want to make this class a dependency-injected service. Should I use AddScoped or AddSingleton? That is to say, should I do this?

services.AddScoped(<ICacheService,CacheService>);

Or this?

services.AddSingleton(<ICacheService,CacheService>);

I have two questions.

One, if I choose AddScoped: my guess is that since my cache service class is merely a wrapper around the MemoryCache, the sole difference would be the slight overhead used to create a cache service object with every web request (AddScoped) vs. one instance for the application (AddSingleton). I'm guessing the .Net runtime will not create a separate instance of the MemoryCache if I use AddScoped.

Two, if I choose AddSingleton, do I need to add "lock" statements inside each method on my cache service around the "MemoryCache.Set" call, like so:

    private readonly object _cachelock = new ();
    public GetEnums GetEnums()
    {
        if (MemoryCache.TryGetValue(CacheKeys.GetEnums, out GetEnums getEnums))
            return getEnums;

        lock(_cachelock)
        {
            if (MemoryCache.TryGetValue(CacheKeys.GetEnums, out getEnums))
                return getEnums;

            getEnums = MyRepository.GetEnums();
            MemoryCache.Set(CacheKeys.GetEnums, getEnums, _enumMemoryCacheEntryOptions);
        }

        return getEnums;
    }

Solution

  • When you register the Memory cache with services.AddMemoryCache() it's added as a Singleton, so it would seem logical to register your service as a Singleton as well.

    Source code

    MemoryCache is also thread safe so there should be no reason to add a lock. If multiple calls to Set() are made for a given key, it will simply update the exist value.

    MemoryCache Docs