jeudi 22 décembre 2016

Caching Pattern for Fetching Collections

Is there a caching strategy/pattern that can be used for fetching multiple items, where only some may be cached? Does it make sense to use a cache in such scenarios?

More Detail

Where the cache would return a single (or known number of) result(s) caching's simple; if it's in the cache we return it, if not we fetch it, add it to the cache, then return it:

//using System.Runtime.Caching;
ObjectCache cache = MemoryCache.Default;
TimeSpan cacheLifetime = new TimeSpan(0, 20, 0);
SomeObjectFinder source = new SomeObjectFinder();
public SomeObject GetById(long id)
{
    return GetFromCacheById(id) ?? GetFromSourceById(id);
}
protected SomeObject GetFromCacheById(long id)
{
    return (SomeObject)cache.Get(id.ToString(),null);
}
protected SomeObject GetFromSourceById (long id)
{
    SomeObject result = source.GetById(id);
    return result == null ? null : (SomeObject)cache.AddOrGetExisting(id.ToString(), result, DateTimeOffset.UtcNow.Add(cacheLifetime), null);
}

However, where we don't know how many results to expect, if we fetched the results from the cache we wouldn't know that we'd fetched everything; only what's been cached. So following the above pattern, if none of or all of the results had been cached we'd be fine; but if half of them had been, we'd get a partial result set.

I was thinking the below may make sense; but I've not played with async/await before, and most of what I've read implies that calling async code from synchronous code is generally considered bad; so this solution's probably not a good idea.

private IEnumerable<SomeObject> GetByPartialNameAsync(string startOfName)
{
    //kick off call to the DB (or whatever) to get the full result set 
    var getResultsTask = Task.Run<IEnumerable<SomeObject>>(async() => await GetFromSourceByPartialNameAsync(startOfName));
    //whilst we leave that to run in the background, start getting and returning the results from the cache
    var cacheResults = GetFromCacheByPartialName(startOfName);
    foreach (var result in cacheResults)
    {
        yield return result;
    }
    //once all cached values are returned, wait for the async task to complete, remove the results we'd already returned from cache, then add the remaining results to cache and return those also
    var results = getResultsTask.GetAwaiter().GetResult();
    foreach (var result in results.Except(cacheResults))
    {
        yield return CacheAddOrGetExistingByName(result.Name, result);
    }
}
protected async Task<IEnumerable<SomeObject>> GetFromSourceByPartialNameAsync(string startOfName)
{
    source.GetByPartialName(startOfName);
}

My assumption is that the answer's going to be "in this scenario either cache everything beforehand, or don't use cache"... but hopefully there's some better option.

Aucun commentaire:

Enregistrer un commentaire