我正在构建一个ASP.NET/Umbraco驱动的网站,它是通过实体框架驱动的非常自定义的数据,我们不得不缓存相当多的数据查询(例如按关键字搜索),因为它是一个繁忙的网站。 / p>
但是当用户创建新数据条目时,我需要清除所有缓存的查询(搜索等...),以便结果中有新条目。
因此,在我的创建,删除和更新方法中,我调用以下方法:
public static void ClearCacheItems()
{
var enumerator = HttpContext.Current.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
HttpContext.Current.Cache.Remove(enumerator.Key.ToString());
}
}
这真的很糟糕吗?我看不出我应该如何清除缓存的项目?
答案 0 :(得分:11)
您使用的方法实际上是清除缓存的正确方法,代码中只有一个小错误。只要原始集合保持不变,枚举器仅有效。因此,虽然代码可能在大多数情况下都可以工作,但在某些情况下可能会出现小错误。最好是使用以下代码,它基本上相同,但不直接使用枚举器。
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]);
答案 1 :(得分:7)
仅清除一个特定功能域的整个ASP.NET缓存似乎有点矫枉过正。
您可以创建一个中间对象,并将所有缓存的查询存储在那里。该对象可能只是字典对象的包装器。所有查询都应使用此对象,而不是直接使用ASP.NET缓存。
然后在需要时将此对象添加到ASP.NET缓存中。当您需要清除查询时,只需访问此对象并清除基础字典。这是一个示例实现:
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();
}
}
如果我的查询表示如下:
public class MyQueryObject
{
....
}
然后,我会像这样使用“区域”缓存:
// put something in this intermediate cache
IntermediateCache<MyQueryObject>.Current.Set("myKey", myObj);
// clear this cache
IntermediateCache<MyQueryObject>.Current.Clear();
答案 2 :(得分:6)
Cache
类的设计人员可以很容易地为其添加Clear
方法。但他们没有,而且是设计 - 因此你的代码糟糕。
如果修改了集合,则一个问题是通过集合枚举的线程影响。这会引发错误。
你真的永远不需要清除整个缓存。如果服务器需要内存,它将清除它。缓存访问是按键(出于某种原因),因此您需要知道您要访问的内容。因此,如果您需要删除和项目,请使用密钥进行操作。
我的建议是以一种清除缓存非常容易的方式设计缓存。例如,按ID对您的缓存项目(创建一个用于保存相关缓存结果的类)进行分组,并使用ID作为密钥。每当更改与该ID相关的内容时,请清除该ID的缓存。 轻松,轻松。
答案 3 :(得分:3)
您是否考虑过使用缓存依赖? Here's the MSDN explanation和那里的一些花絮:
在ASP.NET应用程序的Cache对象中存储的项与文件,缓存键,其中一个数组或另一个CacheDependency对象之间建立依赖关系。 CacheDependency类监视依赖关系,以便当它们中的任何一个发生更改时,将自动删除缓存的项目。
// 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.");
this very enthusiastic fellow更多地解释了它。
答案 4 :(得分:3)
是的,永远不要遍历缓存并删除项目。它会给你一个痛苦的世界(我最近经历过这个)。因为您将收到错误“集合已被修改,枚举可能无法执行”。
我也有一个搜索缓存,我没有设置到期时间 - 我需要时手动无效。
技巧是将数据保存到“桶”中。那个“桶”通常是父标识符,或者如果你想进入数据库术语,那就是外键。
因此,如果您需要清除给定“客户”的所有“订单”,则缓存密钥应为CustomerId。在我的情况下,我缓存ReadOnlyCollection<T>
。所以我不需要循环,只需删除它,然后添加新的副本。
另外,为了双重安全 - 我使用 ReaderWriterLockSlim 来使缓存无效。当我知道它需要被无效时,我首先调用数据库,获取写锁,刷新并刷新相关的缓存,然后关闭写锁。
这样就完全无缝了。