⚠️ Warning: Don't cache lazy IEnumerable<T> in .NET

(The same is true for IAsyncEnumerable<T>)

A few days ago I came across a code where a developer used .NET MemoryCache to cache data retrieved from the database. Sounds boring, but... every time the cache needed to return a result, a call to the database was made.

Let's take a look at a simplified example:

var serviceProvider = new ServiceCollection().AddMemoryCache().BuildServiceProvider();
var randomizer = Random.Shared;
var memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();

// NOTE: we cache the result of GetData()
var data1 = memoryCache.GetOrCreate("key", _ => GetDataFromDb()).ToList();
var data2 = memoryCache.GetOrCreate("key", _ => GetDataFromDb()).ToList();

IEnumerable<int> GetDataFromDb()
{
    // This method will be called twice!
    yield return randomizer.Next();
}

The problem here is that we cache not an array or collection as it may look at first glance, but a lazy IEnumerable, which is calculated not when we call the GetDateFromDb() method, but when we enumerate this lazy thing by calling ToList().

As a result, the method GetDataFromDb() gets called twice (this is what we wanted to avoid with the help of the cache), and also data1 and data will represent different lists with different values.

Solution

Cache the actual result enumerated result instead of lazy IEnumerable<T>:

var serviceProvider = new ServiceCollection().AddMemoryCache().BuildServiceProvider();
var randomizer = Random.Shared;
var memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();

// NOTE: we cache GetData().ToList()
var data1 = memoryCache.GetOrCreate("key", _ => GetDataFromDb().ToList());
var data2 = memoryCache.GetOrCreate("key", _ => GetDataFromDb().ToList());

IEnumerable<int> GetDataFromDb()
{
    yield return randomizer.Next();
}

Now, the behavior will be as expected: the GetDataFromDb() method gets called only once and data1 equals data2.

Thank you for reading and be careful with the cache.

Cheers!

Did you find this article valuable?

Support Aleksei Zagoskin by becoming a sponsor. Any amount is appreciated!