使用ASP.NET清除缓存的最有效方法
本文关键字:有效 方法 缓存 ASP NET 清除 使用 | 更新日期: 2023-09-27 17:58:19
我正在构建一个ASP.NET/Umbraco支持的网站,它是通过实体框架驱动的非常自定义的数据,我们不得不缓存相当多的数据查询(例如按关键字搜索),因为它是一个繁忙的网站。
但是,当用户创建新的数据条目时,我需要清除所有缓存的查询(搜索等),以便在结果中显示新条目。
因此,在我的创建、删除和更新方法中,我调用以下方法:
public static void ClearCacheItems()
{
var enumerator = HttpContext.Current.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
HttpContext.Current.Cache.Remove(enumerator.Key.ToString());
}
}
这真的很糟糕吗?我看不出我应该如何清除缓存的项目?
您使用的方法实际上是清除缓存的正确方法,代码中只有一个小"错误"。只要原始集合保持不变,枚举器仅是有效的。因此,尽管代码可能在大多数情况下都能工作,但在某些情况下可能会出现小错误。最好使用以下代码,这些代码本质上是相同的,但不直接使用枚举器。
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]);
只为一个特定的功能域清除整个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();
对于Cache
类的设计者来说,向其添加Clear
方法是非常容易的。但他们没有,而且这是设计出来的,因此您的代码是糟糕的。
一个问题是如果集合被修改,则枚举集合的线程含义。这会导致一个错误。
你真的不应该清除整个缓存。如果服务器需要内存,它会清除内存。缓存访问是键的(有原因),因此您需要知道要访问什么。因此,如果您需要删除和项目,请按键执行。
更新
我的建议是以一种非常容易清除缓存的方式来设计缓存。例如,按ID对缓存项进行分组(创建一个类来保存相关的缓存结果),并将该ID用作键。每当与该ID相关的内容发生更改时,请清除该ID的缓存。简单,peasy
您是否考虑过使用缓存依赖项?以下是MSDN的解释和一些花絮:
在存储在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.");
这个非常热情的家伙解释了更多关于它的信息。
是的,永远不要在缓存中循环并删除项目。它会给你一个痛苦的世界(我最近经历过)。您将得到一个错误"集合已修改,枚举可能无法执行"。
我还有一个用于搜索的缓存,并且我不设置过期时间——我会在需要时手动使其无效。
诀窍是将数据保存在"桶"中。这个"bucket"通常是父标识符,或者如果您想了解数据库术语,则是外键。
因此,如果您需要清除给定"Customer"的所有"Orders",那么缓存键应该是CustomerId。在我的情况下,我缓存了一个ReadOnlyCollection<T>
。所以我不需要循环,只需删除它,然后添加新的副本。
此外,为了双重安全,我使用ReaderWriterLockSlim来使缓存无效。当我知道它需要无效时,我首先调用数据库,获得写锁,刷新相关缓存,然后关闭写锁。
这样一切都是无缝的。