Ability to set CacheSettings depending on the result of the valueFactory
What problem does the feature solve?
I would like to be able to set the CacheSettings depending on the result of some logic that's run in the valueFactory of the GetOrSetAsync method. This would function similarly to the IMemoryCache.GetOrCreateAsync provided by Microsoft.
The reason this is important to me is, when an exception is thrown, or some logic fails inside the valueFactory, I want the data to not be cached.
How would you use/interact with the feature? (if applicable)
var result = await _cache.GetOrSetAsync<EntityResult<DataModel>>(key,
async entry =>
{
try
{
var data = await GetData(model);
entry.AbsoluteExpirationRelativeToNow = _cacheTimeSpan;
return new EntityResult<DataModel>(data);
}
catch (Exception ex)
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromTicks(1);
return new EntityResult<DataModel>().AddError(ex);
}
});
How would you call it in your own code?
See code sample above
Are there constraints that would need to be in place with this feature?
None that can think of - it may be a breaking change, or change how the stacks work.
If I misunderstand how I'm supposed to use this, please let me know. We only do it this way because of some bugs we had where the valueFactorys were being run more than once when the database methods threw an exception.
Hey @celluj34 - thanks for raising this feature request! Sounds like a perfectly reasonable idea - currently I propagate the exception because I didn't like the idea of swallowing by default however having some mechanism to selectively skip that would be pretty useful.
I've got an idea of how I might be able to do something to support this with a fluent-like API which might be an easier path forward than passing control to within the value factory itself but either way, being able to opt-out of exceptions being bubbled up is a useful feature to have.
Another thought I had, what about adding an overload for the CacheSettings parameters that takes a Func<T, CacheSettings>? Then we could decide how to set based on the value generator. Or maybe all of the above! Just throwing that out there.
Sorry hit the wrong button!
+1 on this for caching Authentication (bearer) tokens which have their expiration time coming on the object that returns from auth endpoints.
It would be something like this (I tried rolling out my own methods of Redis Caching, using RedLock for synchronization between Redis Nodes, not sure if it would work):
public async Task<T> GetOrAddAsync<T>(string key, Func<T> itemCreationFunc, Func<T, TimeSpan> itemBasedExpiryFunc)
{
if (key == null) throw new ArgumentNullException(nameof(key));
var bytes = await _distributedCache.GetAsync(key);
if (bytes != null)
{
return JsonSerializer.Deserialize<T>(bytes.AsSpan()) ?? Activator.CreateInstance<T>();
}
using (var redLock = await _distributedLockFactory.CreateLockAsync($"redis-cache-lock-key-{key}", TimeSpan.FromSeconds(5)))
{
var obj = itemCreationFunc();
var serializedObj = JsonSerializer.SerializeToUtf8Bytes(obj);
var expiration = itemBasedExpiryFunc(obj);
_distributedCache.Set(key, serializedObj, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = expiration });
return obj;
}
}