缓存项的过期时间
ASP.NET支持二种缓存项的过期策略:绝对过期和滑动过期。
1. 绝对过期,这个容易理解:就是在缓存放入Cache时,指定一个具体的时间。当时间到达指定的时间的时,缓存项自动从Cache中移除。
2. 滑动过期:某些缓存项,我们可能只希望在有用户在访问时,就尽量保留在缓存中,只有当一段时间内用户不再访问该缓存项时,才移除它, 这样可以优化内存的使用,因为这种策略可以保证缓存的内容都是【很热门】的。 操作系统的内存以及磁盘的缓存不都是这样设计的吗?而这一非常有用的特性,Cache也为我们准备好了,只要在将缓存项放入缓存时, 指定一个滑动过期时间就可以实现了。
以上二个选项分别对应Add, Insert方法中的DateTime absoluteExpiration, TimeSpan slidingExpiration这二个参数。
注意:这二个参数都是成对使用的,但不能同时指定它们为一个【有效】值,最多只能一个参数值有效。 当不使用另一个参数项时,请用Cache类定义二个static readonly字段赋值。
这二个参数比较简单,我就不多说了,只说一句:如果都使用Noxxxxx这二个选项,那么缓存项就一直保存在缓存中。(或许也会被移除)
缓存项的依赖关系 - 依赖其它缓存项
ASP.NET Cache有个很强大的功能,那就是缓存依赖。一个缓存项可以依赖于另一个缓存项。 以下示例代码创建了二个缓存项,且它们间有依赖关系。首先请看页面代码:
<p>Key1 的缓存内容:<%= HttpRuntime.Cache["key1"] %></p>
<hr />
<form action="FileDependency.aspx" method="post">
<input type="submit" name="SetKey1Cache" value="设置Key1的值" />
<input type="submit" name="SetKey2Cache" value="设置Key2的值" />
</form>
</body>
页面后台代码:
{
[SubmitMethod(AutoRedirect=true)]
private void SetKey1Cache()
{
SetKey2Cache();
CacheDependency dep = new CacheDependency(null, new string[] { "key2" });
HttpRuntime.Cache.Insert("key1", DateTime.Now.ToString(), dep,
Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
}
[SubmitMethod(AutoRedirect=true)]
private void SetKey2Cache()
{
HttpRuntime.Cache.Insert("key2", Guid.NewGuid().ToString());
}
}
当运行这个示例页面时,运行结果如下图所示, 点击按钮【设置Key1的值】时,将会出现缓存项的内容(左图)。点击按钮【设置Key2的值】时,此时将获取不到缓存项的内容(右图)。

根据结果并分析代码,我们可以看出,在创建Key1的缓存项时,我们使用了这种缓存依赖关系:
所以,当我们更新Key2的缓存项时,Key1的缓存就失效了(不存在)。
不要小看了这个示例。的确,仅看这几行示例代码,或许它们实在是没有什么意义。 那么,我就举个实际的使用场景来说明它的使用价值。

上面这幅图是我写的一个小工具。在示意图中,左下角是一个缓存表CacheTable,它由一个叫Table1BLL的类来维护。 CacheTable的数据来源于Table1,由Table1.aspx页面显示出来。 同时,ReportA, ReportB的数据也主要来源于Table1,由于Table1的访问几乎绝大多数都是读多写少,所以,我将Table1的数据缓存起来了。 而且,ReportA, ReportB这二个报表采用GDI直接画出(由报表模块生成,可认是Table1BLL的上层类),鉴于这二个报表的浏览次数较多且数据源是读多写少, 因此,这二个报表的输出结果,我也将它们缓存起来。
在这个场景中,我们可以想像一下:如果希望在Table1的数据发生修改后,如何让二个报表的缓存结果失效?
让Table1BLL去通知那二个报表模块,还是Table1BLL去直接删除二个报表的缓存?
其实,不管是选择前者还是后者,当以后还需要在Table1的CacheTable上做其它的缓存实现时(可能是其它的新报表), 那么,势必都要修改Table1BLL,那绝对是个失败的设计。 这也算是模块间耦合的所带来的恶果。
幸好,ASP.NET Cache支持一种叫做缓存依赖的特性,我们只需要让Table1BLL公开它缓存CacheTable的KEY就可以了(假设KEY为 CacheTableKey), 然后,其它的缓存结果如果要基于CacheTable,设置一下对【CacheTableKey】的依赖就可以实现这样的效果: 当CacheTable更新后,被依赖的缓存结果将会自动清除。这样就彻底地解决了模块间的缓存数据依赖问题。
缓存项的依赖关系 - 文件依赖
在上篇博客【在.net中读写config文件的各种方法】的结尾, 我给大家留了一个问题:
我希望在用户修改了配置文件后,程序能立刻以最新的参数运行,而且不用重启网站。
今天我就来回答这个问题,并给出所需的全部实现代码。
首先,我要说明一点:上次博客的问题,虽然解决方案与Cache的文件依赖有关,但还需与缓存的移除通知配合使用才能完美的解决问题。 为了便于内容的安排,我先使用Cache的文件依赖来简单的实现一个粗糙的版本,在本文的后续部分再来完善这个实现。
先来看个粗糙的版本。假如我的网站中有这样一个配置参数类型:
/// 模拟网站所需的运行参数
/// </summary>
public class RunOptions
{
public string WebSiteUrl;
public string UserName;
}
我可以将它配置在这样一个XML文件中:
<RunOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<WebSiteUrl>http://www.cnblogs.com/fish-li</WebSiteUrl>
<UserName>fish li</UserName>
</RunOptions>
再来一个用于显示运行参数的页面:
<p>WebSiteUrl: <%= WebSiteApp.RunOptions.WebSiteUrl %></p>
<p>UserName: <%= WebSiteApp.RunOptions.UserName %></p>
</body>
下面的代码就可以实现:在XML修改后,浏览页面就能立即看到最新的参数值:
{
private static readonly string RunOptionsCacheKey = Guid.NewGuid().ToString();
public static RunOptions RunOptions
{
get
{
// 首先尝试从缓存中获取运行参数
RunOptions options = HttpRuntime.Cache[RunOptionsCacheKey] as RunOptions;
if( options == null ) {
// 缓存中没有,则从文件中加载
string path = HttpContext.Current.Server.MapPath("~/App_Data/RunOptions.xml");
options = RwConfigDemo.XmlHelper.XmlDeserializeFromFile<RunOptions>(path, Encoding.UTF8);
// 把从文件中读到的结果放入缓存,并设置与文件的依赖关系。
CacheDependency dep = new CacheDependency(path);
// 如果您的参数较复杂,与多个文件相关,那么也可以使用下面的方式,传递多个文件路径。
//CacheDependency dep = new CacheDependency(new string[] { path });
HttpRuntime.Cache.Insert(RunOptionsCacheKey, options, dep);
}
return options;
}
}
}
注意:这里仍然是在使用CacheDependency,只是我们现在是给它的构造函数的第一个参数传递要依赖的文件名。
在即将结束对缓存的依赖介绍之前,还要补充二点:
1. CacheDependency还支持【嵌套】,即:CacheDependency的构造函数中支持传入其它的CacheDependency实例,这样可以构成一种非常复杂的树状依赖关系。
2. 缓存依赖的对象还可以是SQL SERVER,具体可参考SqlCacheDependency