I have an asp.net function which is used as datasource of report. When I first run the function, I cached the dataset and found the cache is created successfully because the cache count is 1. However, when I reenter the function again, I cannot get the cached content. And the cache count is zero. Seems the cache is cleared for some reason. How can I find out the reason the cache count is zero when reentering the page and how can I make the cache work? Here is my code:
//using System.Web.Caching;
using System.Runtime.Caching;
namespace Sustainability.BusinessObject.Client
{
public class ReportManager
{
protected static MemoryCache CACHE = new MemoryCache("MySQLDataProvider_Cache");
public static DataSet KPISummaryReport(int companyID, int fromYear, int fromMonth, int toYear, int toMonth, string locationIDs, bool hideIfNoValue, string lang)
{
HttpResponseMessage result = null;
DataSet ds = null;
try
{
string cacheKey = "kpi_" + companyID + "_" + fromYear + "_" + fromMonth + "_" + toYear + "_" + toMonth + "_" + locationIDs;
Logger.Log(string.Format("Cache count of reentering the code {0}", CACHE.GetCount()));
ds = CACHE.Get(cacheKey) as DataSet;
if (ds != null)
{
return ds;
}
else
{
ds = Util.GetData(_sustainabilityServiceURL, requestUri, out result);
var policy = new CacheItemPolicy { AbsoluteExpiration = DateTime.Now.AddMinutes(60d) };
CACHE.Add(cacheKey, ds, policy);
DataSet ds1 = CACHE.Get(cacheKey) as DataSet;
if (ds1 != null)
{
Logger.Log("Create Cache Succesfully");
}
else
{
Logger.Log("Create Cache Failed");
}
Logger.Log(string.Format("Cache count after create cache {0}",CACHE.GetCount()));
}
}
}
Check the database you use for cache, my first guess is that nothing is actually stored.
With IoC, the proper setup would look something in the lines of adding a distributed cache to services
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = _config["SQLDataProvider_Cache"];
options.SchemaName = "dbo";
options.TableName = "TestCache";
});
There are two types of cache policies you can use:
(DateTimeOffSet) CacheItemPolicy.AbsoluteExpiration expires after fixed time from initial set
(DateTimeOffSet) CacheItemPolicy.SlidingExpiration expires after fixed time from last access
Typically you would want to use the SlidingExpiration one, but as you define absolute then registration would
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
IApplicationLifetime lifetime, IDistributedCache cache)
{
lifetime.ApplicationStarted.Register(() =>
{
var currentTimeUTC = DateTime.UtcNow.ToString();
var encodedCurrentTimeUTC = Encoding.UTF8.GetBytes(currentTimeUTC);
var options = new DistributedCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
cache.Set("cachedTimeUTC", encodedCurrentTimeUTC, options);
});
The repository itself should not contain static members (or only logger). Adding an interface for this repository would improve testing and mocking out features as well as being default way to pass this with IoC
public class ReportRepository : IReportRepository
{
private readonly IAppCache _cache;
private readonly ILogger _logger;
private SomeService _service;
public string ServiceUrl { get; set; }
public string RequestUri { get; set; }
public ReportRepository(IAppCache appCache, ILogger<ShowDatabase> logger, SomeService service)
{
_service = service;
_logger = logger;
_cache = appCache;
}
public async Task<List<Show>> GetShows()
{
var cacheKey = "kpi_{companyID}_{fromYear}_{fromMonth}_{toYear}_{toMonth}_{locationIDs}";
Func<Task<List<DataSet>>> reportFactory = () => PopulateReportCache();
//if you do it in DistributedCacheEntryOptions you do not need to set it here
var absoluteExpiration = DateTimeOffset.Now.AddHours(1);
var result = await _cache.GetOrAddAsync(cacheKey, reportFactory, absoluteExpiration);
return result;
}
private async Task<List<DataSet>> PopulateReportCache()
{
List<DataSet> reports = await _service.GetData(ServiceUrl, RequestUri, out result);
_logger.LogInformation($"Loaded {reports.Count} report(s)");
return reports.ToList(); //I would have guessed it returns out parameter result ...
}
}
For more info check Cache in-memory in ASP.NET Core.
In .net 4.5 use LazyCache
var cache = new CachingService();
var cachedResults = cache.GetOrAdd(key, () => SetMethod());
to use sql server, mysql, postgres, etc then just implement ObjectCache and pass it to the constructor of the caching service. Here is a guide that also list few more common ones like Redis. It defaults to 20 minutes sliding expiration and you can set it by changing the policy.