This came from a need to throttle API calls to a 3rd party service. I wanted a simple way to cache results from previous calls with a dictionary key in my SQL Server database, and set a time limit after which the cache expires. After that, the live data source can be queried again. If I call a service within a specified time span, then I get the cached data.
This uses the DbDictionary class from my Dapper.CX project. This project builds upon this with the DbCache class. DbCache
is an abstract class so you must provide your own json serialization. See the integration tests to see a sample use along with the SampleDbCache implementation. The tests use my CloudObjects service. Here's the essence of it. Let's say you have this API call:
var cloudObj = await client.GetAsync<SampleObject>(objectName);
return cloudObj.Object;
Let's say you're okay fetching live data from this at most every hour. You would use DbCache
to store the call result with a string key. This example says: get SampleObject
with key object1
a maximum of once per hour. If less than an hour has passed since the last call, then the cached data is returned. Otherwise, the live data is returned, and the cache is updated again and used for another hour.
var fetched = await cache.GetAsync("object1",
async () =>
{
var cloudObj = await client.GetAsync<SampleObject>(objectName);
return cloudObj.Object;
}, TimeSpan.FromMinutes(60));
To determine if the returned data came from the cache or live source, use the Source property.
If your API call returns a json array, you can use SetEachAsync
to store individual array elements as separate cache rows. This lets you get these individual elements later without enumerating the original array again. There are two overloads, one that stores your original json data as-is, and another lets you transform or modify the json in some way. Both overloads return Dictionary<string, int>
with all the keys and generated cache row Id
values.
Data saved with SetEachAsync
has no expiration timespan or date. Since DbCache
derives from DbDictionary
, you can fetch this cache data GetAsync.
Here's a hypothetical example that shows SetEachAsync
caching some API call array results. The array is drawings
. Each element has a FullUrl
property that is assumed to be unique. Each element is converted to a hypothetical ImportedDrawingInfo
object.
await _cache.SetEachAsync(drawings, d => d.FullUrl, (dwg) => new ImportedDrawingInfo()
{
CompanyId = CompanyId.Value,
CompanyName = companyDictionary[CompanyId.Value],
ProjectId = ProjectId.Value,
ProjectName = projectDictionary[ProjectId.Value],
DrawingSetId = DrawingSetId.Value,
DrawingSetName = drawingSetDictionary[DrawingSetId.Value],
RevInfo = dwg
});
I can later fetch the individual elements as needed using the FullUrl
as a key. GetAsync
will deserialize the json into its strongly-typed ImportedDrawingInfo
form. In this example request.SourceUri
represents the FullUrl
key:
var revInfo = await _cache.GetAsync<ImportedDrawingInfo>(request.SourceUri);
Although I made this with API calls in mind, you can cache anything you can fetch or query with the accessor
argument of GetAsync. Results of database queries, for example, will work fine to cache as strong-typed json.
DbCacheLibrary.DbCache DbCache.cs
- string KeyPrefix
- ObjectSource Source
- Task<TValue> GetAsync (string key, Func<Task> accessor, TimeSpan maxAge)
- Task<TValue> GetAsync (string key, Func<Task> accessor, DateTime expireAfterUtc)
- Task<TValue> QueryAsync (string key)
- Task<Dictionary<string, int>> SetEachAsync (IEnumerable items, Func<TItem, string> keyAccessor)
- Task<Dictionary<string, int>> SetEachAsync (IEnumerable items, Func<TSource, string> keyAccessor, Func<TSource, TTarget> saveAsTarget)