问题
I am building an ASP.NET/Umbraco powered website which is very custom data driven via entity framework, we are having to cache quite a lot of the data queries (For example searches by keyword) as it's a busy site.
But when a user creates a new data entry, I need to clear all the cached queries (Searches etc..) so the new entry is available in the results.
So in my create, delete and update methods I am calling the following method:
public static void ClearCacheItems()
{
var enumerator = HttpContext.Current.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
HttpContext.Current.Cache.Remove(enumerator.Key.ToString());
}
}
Is this really bad? I can't see how else I am supposed to clear the cached items?
回答1:
The method you use is actually the correct way to clear your cache, there is just one minor 'error' in your code. The enumerator only is valid as long as the original collection remains unchanged. So while the code might work most of the time, there might be small errors in certain situations. Best is to use the following code, which does essentially the same, but does not use the enumerator directly.
List<string> keys = new List<string>();
IDictionaryEnumerator enumerator = Cache.GetEnumerator();
while (enumerator.MoveNext())
keys.Add(enumerator.Key.ToString());
for (int i = 0; i < keys.Count; i++)
Cache.Remove(keys[i]);
回答2:
Clearing the whole ASP.NET cache for just one specific functional domain seems a bit overkill.
You could create an intermediate object, and store all your cached queries in there. This object could be just a wrapper on a dictionary object. All queries should use this object instead of playing with the ASP.NET cache directly.
Then you add this object to the ASP.NET Cache when you need it. When you need to clear the queries, just get to this object and clear the underlying dictionary. Here is a sample implentation:
public sealed class IntermediateCache<T>
{
private Dictionary<string, T> _dictionary = new Dictionary<string, T>();
private IntermediateCache()
{
}
public static IntermediateCache<T> Current
{
get
{
string key = "IntermediateCache|" + typeof(T).FullName;
IntermediateCache<T> current = HttpContext.Current.Cache[key] as IntermediateCache<T>;
if (current == null)
{
current = new IntermediateCache<T>();
HttpContext.Current.Cache[key] = current;
}
return current;
}
}
public T Get(string key, T defaultValue)
{
if (key == null)
throw new ArgumentNullException("key");
T value;
if (_dictionary.TryGetValue(key, out value))
return value;
return defaultValue;
}
public void Set(string key, T value)
{
if (key == null)
throw new ArgumentNullException("key");
_dictionary[key] = value;
}
public void Clear()
{
_dictionary.Clear();
}
}
If my query is represented like this:
public class MyQueryObject
{
....
}
Then, I would use the "regional" cache like this:
// put something in this intermediate cache
IntermediateCache<MyQueryObject>.Current.Set("myKey", myObj);
// clear this cache
IntermediateCache<MyQueryObject>.Current.Clear();
回答3:
It would have been very easy for the designers of the Cache
class to add a Clear
method to it. But they have not and it was by design - hence your code is bad.
One problem is threading implications of enumerating through a collection if the collection is modified. That would throw an error.
You really should never need to clear the whole of the cache. If server needs memory, it will clear it. Cache access is by key (for a reason) hence you need to know what you are trying to access. So if you need to remove and item, do it by a key.
UPDATE
My advice would be to design the cache in a way that clearing the cache would be very easy. For example group your cache items (create a class to hold related cache results) by an ID and use the ID as the key. Whenever something related to that ID is changed, clear the cache for that ID. Easy, peasy.
回答4:
Have you considered using a Cache Dependency? Here's the MSDN explanation and a few tidbits from there:
Establishes a dependency relationship between an item stored in an ASP.NET application's Cache object and a file, cache key, an array of either, or another CacheDependency object. The CacheDependency class monitors the dependency relationships so that when any of them changes, the cached item will be automatically removed.
// Insert the cache item.
CacheDependency dep = new CacheDependency(fileName, dt);
cache.Insert("key", "value", dep);
// Check whether CacheDependency.HasChanged is true.
if (dep.HasChanged)
Response.Write("<p>The dependency has changed.");
else Response.Write("<p>The dependency has not changed.");
And this very enthusiastic fellow explains some more about it.
回答5:
Yep, never loop through the cache and remove items. It will give you a world of pain (I went through this recently). As you'll get an error "collection was modified, enumeration may not execute".
I also have a cache for searching, and I don't set an expiry - I manually invalidate when required.
The trick is the data is held into "buckets". That "bucket" is usually the parent identifier, or if you want to get into database terms, the foreign key.
So if you need to clear all "Orders" for a given "Customer", the cache key should be the CustomerId. In my case, I cache a ReadOnlyCollection<T>
. So I don't need to loop, just remove it, then add in the fresh copy.
Also, to be doubly safe - I use a ReaderWriterLockSlim to invalidate the cache. When I know it needs to be invalidated, I first call the database, get a write lock, flush and refresh the relevant cache, then close the write lock.
That way it's all seamless.
来源:https://stackoverflow.com/questions/6212676/most-efficient-way-of-clearing-cache-using-asp-net