使用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());
    }
}

这真的很糟糕吗?我看不出我应该如何清除缓存的项目?

使用ASP.NET清除缓存的最有效方法

您使用的方法实际上是清除缓存的正确方法,代码中只有一个小"错误"。只要原始集合保持不变,枚举器仅是有效的。因此,尽管代码可能在大多数情况下都能工作,但在某些情况下可能会出现小错误。最好使用以下代码,这些代码本质上是相同的,但不直接使用枚举器。

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来使缓存无效。当我知道它需要无效时,我首先调用数据库,获得写锁,刷新相关缓存,然后关闭写锁。

这样一切都是无缝的。