In Memory caching in .NET Core
What is in-memory cache?
A powerful technique that stores data within the memory of your server, making data retrieval lightning-fast.
In-memory caching is a common scaling technique that can enhance performance for frequently needed data.
When should we use it?
We should use in-memory caching:
- When we need to access some data frequently that doesn’t change.
- While scaling our applications for performance.
- Ideal for high-traffic applications.
Pros and cons of using in-memory cache
Benefits:
- Speed
- Scalability
- Reliability
Cons:
- Volatility: Data stored in memory is lost when the application ends or the server restarts.
- Cost: More RAM is required, which can increase costs.
- Complexity: Implementing caching mechanisms can be complex and requires careful planning.
How to implement it in .NET 6.0
Add service of memory cache in Program.cs file of your API to enable in memory cache.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
Inject IMemoryCache in your desired class where you are going to add caching code
private readonly IMemoryCache _cache;
private readonly ILogger<PrimeNumbersService> _logger;
public PrimeNumbersService(IMemoryCache cache, ILogger<PrimeNumbersService> logger)
{
_logger = logger;
_cache = cache;
}
The next step is how to set the cache, these are a few available methods that we can use:
namespace Microsoft.Extensions.Caching.Memory
{
public static class CacheExtensions
{
public static object Get(this IMemoryCache cache, object key) { /* implementation */ }
public static TItem Get<TItem>(this IMemoryCache cache, object key) { /* implementation */ }
public static bool TryGetValue<TItem>(this IMemoryCache cache, object key, out TItem value) { /* implementation */ }
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value) { /* implementation */ }
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, DateTimeOffset absoluteExpiration) { /* implementation */ }
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, TimeSpan absoluteExpirationRelativeToNow) { /* implementation */ }
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, IChangeToken expirationToken) { /* implementation */ }
public static TItem Set<TItem>(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options) { /* implementation */ }
public static TItem GetOrCreate<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, TItem> factory) { /* implementation */ }
public static async Task<TItem> GetOrCreateAsync<TItem>(this IMemoryCache cache, object key, Func<ICacheEntry, Task<TItem>> factory) { /* implementation */ }
}
}
We are going to use TryGetValue and Set for retrieving the cached values and setting it respectively.
I have created a DTO to store the data in the cache.
public record PrimeCacheResult(long number, bool isPrime);
Let’s see now how can we use it, we need to pass the key while getting the cached results.
Similarly while setting the cache we have to pass the key( which in our case is a number in string format), data object, and cache entry options
string key = $"{number}";
if (cache.TryGetValue(key, out PrimeCacheResult primeNumber) && primeNumber != null)
{
_logger.Log(LogLevel.Information, "Prime number found in cache");
}
else
{
_logger.Log(LogLevel.Information, "Prime number not found in cache");
bool isCurrentNumberPrime = number.IsPrime();
PrimeCacheResult result = new PrimeCacheResult(number, isCurrentNumberPrime);
var cacheEntryOptions = _cacheOptions.GetMemoryOptions(
slidingExpirationTime,
absoluteExpirationTime,
cacheEntryValue,
cachePriority
);
_cache.Set(key, result, cacheEntryOptions);
}
What are the Cache Entry Options
hese options help us in defining the behavior of our cached data, it has two important things worth mentioning:
- Sliding Expiration Time: It represents the max timespan value for which a cached value can be inactive, which later on can be removed. Suppose a cache value can be inactive for 5 minutes
- Absolute Expiration Time: It is the duration after which a cache value would be automatically removed, suppose expire cache values after one hour
You can explore other methods of caching for example to remove the cache we can use the Remove method and pass it to the cached object.
How to handle concurrent requests
We can handle concurrent requests either by locking the thread or using semaphores, I prefer to use semaphores because it gives us the facility of setting the maximum number limit for concurrent requests.
We can add it like this: private readonly SemaphoreSlim semaphore = new(1, 10000);
public async Task<bool> VerifyPrimeNumber(long number)
{
try
{
await semaphore.WaitAsync();
// Add your prime number verification logic here
// Example:
bool isPrime = IsPrime(number);
return isPrime;
}
catch (Exception ex)
{
// Optionally log the exception here
throw;
}
finally
{
semaphore.Release();
}
}
Cache Scenario with GitHub Code
I have implemented caching for a web API where the user receives the requests and verifies if a number is prime. Meanwhile, it stores the results of already requested numbers.Get the code from my GitHub Repo
Whenever you're ready, there are 3 ways I can help you:
- Subscribe to my youtube channel : For in-depth tutorials, coding tips, and industry insights.
- Promote yourself to 9,000+ subscribers : By sponsoring this newsletter
- Patreon community : Get access to all of my blogs and articles at one place