0

I have a method that will expire null items immediately, however I wanted to know if there w as better way to do this for all memory cache items instead of repeating the same code over and over

output = _cache.GetOrAdd("GetRecordUri" + 123, entry =>
{
    var record = internalGetRecordUri();
    if (record == null)
        // expire immediately
        entry.AbsoluteExpirationRelativeToNow = new TimeSpan(-1, 0, 0, 0);
    else
        entry.AbsoluteExpirationRelativeToNow = new TimeSpan(1, 0, 0, 0);
    return record;
});

The code in bold seems redundant Is there an extension that I can use that will do the same?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
CodeMan03
  • 570
  • 3
  • 18
  • 43
  • Your code throws to me an `ArgumentOutOfRangeException: The relative expiration value must be positive.` on the line that assigns the `entry.AbsoluteExpirationRelativeToNow` to the value `new TimeSpan(-1, 0, 0, 0)`. – Theodor Zoulias Jun 13 '22 at 19:10
  • Same exception for me, this does not work in .NET 6. `entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(-1);` fails, as does `entry.AbsoluteExpirationRelativeToNow = TimeSpan.Zero;` But if you do `entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(-1);` then you get away with it. It's still a bit of hack. – Anthony Jul 08 '23 at 10:56

2 Answers2

0

You can place this check in separate method:

public void SetAbsoluteExpiration(Entry entry, object value) // change Entry to correct type
{
    if (value is null) // or if (value == null)
        // expire immediately
        entry.AbsoluteExpirationRelativeToNow = new TimeSpan(-1, 0, 0, 0);
    else
        entry.AbsoluteExpirationRelativeToNow = new TimeSpan(1, 0, 0, 0);
}

And call it everywhere you need:

 output = _cache.GetOrAdd(
    "GetRecordUri" + 123, entry => {
        var record = internalGetRecordUri();
        
        SetAbsoluteExpiration(entry, record);

        return record;
    });
Roman
  • 11,966
  • 10
  • 38
  • 47
  • 1
    Simple enough. Thanks – CodeMan03 Jun 13 '22 at 16:40
  • This does not work in .NET 6: MemoryCache's validation prevents zero or negative TimeSpans. It throws `ArgumentOutOfRangeException: The relative expiration value must be positive.` – Anthony Jul 11 '23 at 07:35
  • @Anthony, maybe you're right, but the question was about code reuse, not about setting negative expiration value. The negative expiration is in question and I didn't change the logic, just extracted to separate method so it can be reused. – Roman Jul 11 '23 at 18:22
  • You can't re-use that code because you can't use it at all, directly or wrapped in a method. The question is about "prevent null items being added to cache" and what goes in the code that does that. AFAIK, it has to be a `throw`. – Anthony Jul 12 '23 at 21:01
  • @Anthony, `however I wanted to know if there w as better way to do this for all memory cache items instead of repeating the same code over and over` - that's in question – Roman Jul 13 '23 at 05:14
0

It would be nice if you could signal in addtemFactory that the value is not to be cached at all, e.g.

output = _cache.GetOrAdd("GetRecordUri_" + id, entry =>
{
    var record = internalGetRecordUri(id);
    if (IsError(record))
        entry.IsValidToCache = false; //  not actual
    return record;
});

But there is no such mechanism, without throwing an exception.

Setting the AbsoluteExpirationRelativeToNow to a zero or negative value does not work, it throws a ArgumentOutOfRangeException:

System.ArgumentOutOfRangeException
  Message=The relative expiration value must be positive. (Parameter 'AbsoluteExpirationRelativeToNow')
Actual value was 00:00:00.
  Source=Microsoft.Extensions.Caching.Memory
  StackTrace:
   at Microsoft.Extensions.Caching.Memory.CacheEntry.set_AbsoluteExpirationRelativeToNow(Nullable`1 value)

Throwing an exception in the addtemFactory, is however supported and effective to prevent caching of the result: "LazyCache Stops you inadvertently caching an exception by removing Lazys that evaluate to an exception". I recommend using a more meaningful exception type than a ArgumentOutOfRangeException

e.g.

output = _cache.GetOrAdd("GetRecordUri_" + id, entry =>
{
    var record = internalGetRecordUri(id);
    if (IsError(record))
        throw new SomeException("GetRecordUri failed for " + id); // actual
    return record;
});

You just have to now think about where the exception is caught.

Anthony
  • 5,176
  • 6
  • 65
  • 87