I'm using IMemoryCache in my .Net Core application. This is how I write/read something to the cache:
List<Track> tracks = null;
if (!_cache.TryGetValue("tracks", out tracks) {
tracks = DbSelect.getAllTracks(groupId, "tracks", true);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromDays(3))
.SetAbsoluteExpiration(DateTime.UtcNow.AddDays(10));
_cache.Set("tracks", tracks, cacheEntryOptions);
}
In some cases I need to add or remove some objects to 'tracks'. This is user based. It should not be the case that if user x adds an object to 'tracks', user y sees this addition in 'tracks'. The basic dataset is the same for every user, but for certain cases I need to extend 'tracks' or remove certain objects.
When I manipulate 'tracks', the cache is also manipulated. Is there a way to make the cache readonly?
I know I can serialize/deserialize 'tracks' to cut the link to the cache, but the cost is too great: 'tracks' is a huge object and it takes too long to serialize/deserialize every time I run a manipulation on 'tracks'...
Related
How to correctly clear IMemoryCache from ASP.NET Core?
I believe this class is missing Clear method, but anyway how to deal with it? In my project I'm caching DocumentRepository's methods for 24 hours where I'm getting lots of rows from database. But sometimes I can change the database, so I want to clear the IMemoryCache as it has got rubbish data.
IMemoryCache indeed lacks a Clear() method, but it's not hard to implement one yourself.
public class CacheWrapper
{
private readonly IMemoryCache _memoryCache;
private CancellationTokenSource _resetCacheToken = new();
public CacheWrapper(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public void Add(object key, object value, MemoryCacheEntryOptions memoryCacheEntryOptions)
{
using var entry = _memoryCache.CreateEntry(key);
entry.SetOptions(memoryCacheEntryOptions);
entry.Value = value;
// add an expiration token that allows us to clear the entire cache with a single method call
entry.AddExpirationToken(new CancellationChangeToken(_resetCacheToken.Token));
}
public void Clear()
{
_resetCacheToken.Cancel(); // this triggers the CancellationChangeToken to expire every item from cache
_resetCacheToken.Dispose(); // dispose the current cancellation token source and create a new one
_resetCacheToken = new CancellationTokenSource();
}
}
Based on https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1#cache-dependencies and https://stackoverflow.com/a/45543023/2078975
There is an easy way to clear it that may not work in all environments, but where it does work, it works well.
If you're getting your IMemoryCache object through dependency injection, there is a good chance you'll actually be receiving a MemoryCache object. MemoryCache does have a method called Compact() that will clear the cache. You can try to cast the object to MemoryCache and, if successful, call Compact.
Example:
string message;
// _memoryCache is of type IMemoryCache and was set via dependency injection.
var memoryCache = _memoryCache as MemoryCache;
if (memoryCache != null)
{
memoryCache.Compact(1);
message ="Cache cleared";
} else {
message = "Could not cast cache object to MemoryCache. Cache NOT cleared.";
}
The cache class and interface don't have any methods for clearing neither ones to iterate over the keys, since it's not meant to be a list and in ASP.NET Core applications one usually use IDistributedCache interface as dependency, since it easier allows you to later change from a local memory cache to a distributed cache (such as memd or Redis).
Instead, if you want to invalidate a specific row, you should remove the cached entry via cache.Remove(myKey).
Of course, this requires you to know the key you want to invalidate. Typically you do that via events. Every time you update an entry in the database, you would fire up an event. This event will be caught by a background service and cause a cache invalidation.
Locally this can be done with any pub/sub library. In distributed scenarios you may want to use pub/sub features of the distributed cache (i.e. Redis).
In cases of lookup tables (where many values are affected), you can have a service refresh the cache (i.e. every 5 to 10 minutes via a background task using a scheduling library such as hangfire or quart.net).
Home work Questions
But one question you should ask yourself: Is it really a good idea to cache documents for 24 hours if they change frequently?
Does the loading of a single document takes so much time, that caching it 24 hours will be worth? Or are shorter times good enough (15, 30, 60 minutes)?
To clear you MemoryCache, just remove your memory cache by key, like this:
memoryCache.Remove("your_key");
I'm using .NET C# for a project.
I have a list of products which I want to cache as they're used company wide. If the products drop out of cache I already know how to lock the cache and rebuild it ok as per the patterns on various authority/blog sites.
In my pages/user controls etc, I might grab a reference to the cache, like this:
var myCacheInstance = cachedProducts
However, I might also want to do something like this:
myCacheInstance.Add(new product(...));
Which will also update the cache as it's the same object.
I have 2 queries.
If I have a reference to the cached object is it guaranteed to remain in cache for the lifetime of my variable?
In the scanario outlined above, how do I go about ensuring integrity? I'm only planning on adding in this instance, but suppose, I was updating and deleting objects as well?
1) If I have a reference to the cached object is it guaranteed to
remain in cache for the lifetime of my variable?
If I right interpret this question: responce is no.
cache.Add("key", new object()); // ADD KEY
var obj = cache["key"]; // GET REFERENCE TO CACHED OBJECT
cache.Remove("key"); // REMOVE OBJECT FROM CACHE
obj.DoSomething(..); //PERFECTLY VALID, STILL WORK ..
2) In the scanario outlined above, how do I go about ensuring
integrity? I'm only planning on adding in this instance, but suppose,
I was updating and deleting objects as well?
Can add bool property like, for example:
public bool IsValid
{
get; private set;
}
when object removed this property is set from the class to false. Just example, iff it really fits your need can tell us only you.
Do not pass around a reference to your cache!
Use an object for your cache and if a clients wants to have the cached items return a new list of your cached items, or a readonly collection.
If you want to add items to the cache, use a method on the cache object and in that method lock the cache and add the item. Same with remove.
question 1: If you pass around references you can not guarantee anything.
question 2: Use an object to cache all your items as I described above.
public class Cache
{
private List<Item> cachedItems = new List<Item>();
public void Add(Item item)
{
lock(cachedItems)
{
cachedItems.Add(item);
}
}
}
hello in order to ensure integrity, you must add key
Cache.Add("YourKey", yourValue)
here you can find helper for all operations
http://johnnycoder.com/blog/2008/12/10/c-cache-helper-class/
For duration or timeout you have this format, where you specify absoluteExpiration
public Object Add (string key, Object value, CacheDependency dependencies,
DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority
priority, CacheItemRemovedCallback onRemoveCallback)
I'm building a repository with caching using spring.net. Can I update/add/delete one item in the cached list without having to rebuild the whole list?
Looking at the documentation and the example project from their site they always clear the cache whenever they update/add/delete one item. Therefore as long as you only read an object or the list of objects the caching works well but it feels stupid having to rebuild the whole cache just because I change one item?
Example:
// Cache per item and a list of items
[CacheResult("DefaultCache", "'AllMovies'", TimeToLive = "2m")]
[CacheResultItems("DefaultCache", "'Movie-' + ID")]
public IEnumerable<Movie> FindAll()
{
return movies.Values;
}
// Update or add an item invalidating the list of objects
[InvalidateCache("DefaultCache", Keys = "'AllMovies'")]
public void Save([CacheParameter("DefaultCache", "'Movie-' + ID")]Movie movie)
{
if (this.movies.ContainsKey(movie.ID))
{
this.movies[movie.ID] = movie;
}
else
{
this.movies.Add(movie.ID, movie);
}
}
Having mutable things stored in the cache seems to me a fountain of horrible side effects. Imho that is what you would need if you want to add/remove entries from a cached list.
The implementation of CacheResultAdvice and InvalidateCacheAdvice allows to store and invalidate an object (key) -> object (value) combination. You could add another layer and retrieve movie per movie but I think that it is just a case of premature optimization (with the opposite effect).
CacheResultAdvice
InvalidateCacheAdvice
Edit:
Btw. if you use a mature ORM look for integrated level2 caching, if you want to avoid hitting the db server: http://www.klopfenstein.net/lorenz.aspx/using-syscache-as-secondary-cache-in-nhibernate
I want to be able to maintain certain objects between application restarts.
To do that, I want to write specific cached items out to disk in Global.asax Application_End() function and re-load them back on Application_Start().
I currently have a cache helper class, which uses the following method to return the cached value:
return HttpContext.Current.Cache[key];
Problem: during Application_End(), HttpContext.Current is null since there is no web request (it's an automated cleanup procedure) - therefore, I cannot access .Cache[] to retrieve any of the items to save to disk.
Question: how can I access the cache items during Application_End()?
If you want to get access to cache object before it will be disposed, you need to use somethink like this to add object to cache:
Import namespace System.Web.Caching to your application where you are using adding objects to cache.
//Add callback method to delegate
var onRemove = new CacheItemRemovedCallback(RemovedCallback);
//Insert object to cache
HttpContext.Current.Cache.Insert("YourKey", YourValue, null, DateTime.Now.AddHours(12), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, onRemove);
And when this object is going to be disposed will be called following method:
private void RemovedCallback(string key, object value, CacheItemRemovedReason reason)
{
//Use your logic here
//After this method cache object will be disposed
}
I strongly urge you to rethink your approach. You may want to describe specifics of what are you trying to do, so we might help you with that.
But if you are totally set on it, then you can simply save values on disk when you actually set them, i.e. your helper class would looks something like this:
public static class CacheHelper
{
public static void SetCache(string key, object value)
{
HttpContext.Current.Cache[key] = value;
if (key == "some special key")
WriteValueOnDisk(value);
}
}
You can access the cache through HttpRuntime.Cache when you don't have an HttpContext available. However, at Application_End, i believe the cache is already flushed.
The solution Dima Shmidt outlines would be the best approach to store your cached values. That is by adding your items to cache with a CacheItemRemovedCallback, and store the values to disk there.
As an alternative solution you could store the data in Application object (Application[key]) or simply create a static class and use it to keep your data within app - in this case the data would sill be available upon Application_End.
im new to the Castle Active Record Pattern and Im trying to get my head around how to effectivley use cache.
So what im trying to do (or want to do) is when calling the GetAll, find out if I have called it before and check the cache, else load it, but I also want to pass a bool paramter that will force the cache to clear and requery the db.
So Im just looking for the final bits.
thanks
public static List<Model.Resource> GetAll(bool forceReload)
{
List<Model.Resource> resources = new List<Model.Resource>();
//Request to force reload
if (forceReload)
{
//need to specify to force a reload (how?)
XmlConfigurationSource source = new XmlConfigurationSource("appconfig.xml");
ActiveRecordStarter.Initialize(source, typeof(Model.Resource));
resources = Model.Resource.FindAll().ToList();
}
else
{
//Check the cache somehow and return the cache?
}
return resources;
}
public static List<Model.Resource> GetAll()
{
return GetAll(false);
}
Take a look at the caching pattern:
http://weblogs.asp.net/ssmith/archive/2003/06/20/9062.aspx
http://stevesmithblog.com/blog/cache-access-pattern-revised/
BTW you're initializing ActiveRecord each time you call GetAll. You have to initialize only once, when your application starts.
Also, it's not good practice generally to explicitly release the cache like that. Instead, use some sort of policy or dependency (see for example SqlDependency)
Also, NHibernate has a pluggable second-level cache.