19.4 高级缓存依赖

具体地讲,ASP.NET缓存支持的依赖项有键依赖项、文件依赖项、SQL依赖项、聚合依赖项与自定义依赖项。其中,键依赖项、文件依赖项与聚合依赖项在前面小节中做了比较详细的讲解,本节就不再阐述。下面主要讨论SQL依赖项与自定义依赖项。

19.4.1 SQL Server 2005与SQL Server 2008缓存依赖

其实,在某些方案中,使用带有SQL依赖项的缓存可显著提高应用程序的性能。例如,假定正在构建一个从数据库显示产品信息的电子商务应用程序。如果不进行缓存,则每当用户要查看产品时,应用程序都必须从数据库请求数据,执行相关的数据库连接、查询等命令。如果查询访问量很大,对于服务器与数据库来讲其耗费的资源是不可估量的。要解决这样的问题,可以根据需要在某一时刻将产品信息缓存一天或者一段时间,由于产品信息已经在内存中,因此可确保较快的响应时间,从而也减少了数据库的访问量。

但是,当数据库的产品信息发生变化时,缓存的产品信息就会失去与数据库中的产品信息的同步,且不同步的时间最长可达设置的缓存时间(如一天)。其实,面对这样的不同步问题,使用SQL缓存依赖项可以缓存产品信息,并创建一个数据库表或行更改的依赖项。当且仅当数据更改时,基于该数据的缓存项便会失效并会从缓存中移除。下次从缓存中请求该项时,如果该项不在缓存中,便可以再次向缓存中添加更新后的版本,并且可确保具有最新的数据。

在ASP.NET中,提供了SqlCacheDependency类用于创建依赖于数据库中表或行的缓存项。当表中或特定行中发生更改时,带有依赖项的项便会失效,并会从缓存中移除。可以在Microsoft SQL Server 7.0、SQL Server 2000、SQL Server 2005与SQL Server 2008中设置表的依赖项。因为Microsoft SQL Server 7.0与SQL Server 2000现在业界用得越来越少,就其实用性,下面对SQL Server 2005与SQL Server 2008缓存依赖做一个详细的阐述。

其实,SQL Server 2005与SQL Server 2008与理想的通知解决方案最为接近,它们实现了一种更改通知模型,可以向订阅了通知的应用程序服务器发送通知,而不是依赖早期版本的SQL Server中必需的轮询模型。如图19-2所示,它们将通知基础结构以消息系统的模式内建在数据库内,称为服务代理。服务代理管理队列,它们是具有相同标准的表、存储过程或者视图等数据库对象。

figure_0699_0512

图 19-2 在SQL Server 2005与SQL Server 2008中监视数据的变化

SQL Server 2005与SQL Server 2008监控对特定SQL命令的结果集的更改。如果数据库中发生了将修改该命令的结果集的更改,依赖项便会使缓存的项失效。此功能使得SQL Server 2005与SQL Server 2008可以提供行级别的通知。

要使用SQL Server 2005与SQL Server 2008缓存依赖,首先要做的就是启用通知,即确保数据库具有enable_broker标记设置。可以用下面的SQL语句来执行这个动作:


use ASPNET4

alter database ASPNET4 set enable_broker


通知支持Select查询和存储过程,支持多个查询和嵌套查询,但必须遵循如下规则:

1)必须提供完全限定的表名,其中包括所有者名称,例如dbo.employee,如果只写成employee就是不正确的。

2)查询不支持聚合操作,例如count()、max()、min()或者average()。

3)查询不能够使用通配符号(*)选择所有列。

如下面就是一个可接受的命令:


select employeename from dbo. employee


下面将通过一个详细的示例来阐述如何使用SQL Server 2005与SQL Server 2008缓存依赖。首先需要在SqlServerDependency.aspx页面里定义三个控件。其中,bt_Update控件用于修改数据库数据,bt_GetData控件用于获取缓存数据情况,lblInfo控件用于显示相关的操作信息。页面代码如代码清单19-1所示。

代码清单19-1 SqlServerDependency.aspx


<%@Page Language="C#"AutoEventWireup="true"

CodeBehind="SqlServerDependency.aspx.cs"

Inherits="_19_1.SqlServerDependency"%>

<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

<title></title>

</head>

<body>

<form id="form1"runat="server">

<div>

<asp:Button ID="bt_Update"runat="server"

Height="24px"Text="修改表"Width="100px"

OnClick="bt_Update_Click"/>

<asp:Button ID="bt_GetData"runat="server"

Height="24px"Text="获取数据"Width="100px"

OnClick="bt_GetData_Click"/>

<br/>

<br/>

<asp:Label ID="lblInfo"runat="server"

BorderStyle="Dotted"BorderWidth="1px"

Font-Size="12px"Width="500px">

</asp:Label>

</div>

</form>

</body>

</html>


下面需要在代码里完成这样几个功能:

首先,在页面的Page_Load事件里创建一个SqlCacheDependency缓存依赖项;然后,在bt_GetData_Click事件里获取缓存数据的存在与否;最后,在bt_Update_Click事件里修改dbo.employee表的employeename字段的值,从而使缓存项自动移除。如代码清单19-2所示。

代码清单19-2 SqlServerDependency.aspx.cs


using System;

using System.Data;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Data.SqlClient;

using System.Web.Caching;

using System.Web.Configuration;

namespace_19_1

{

public partial class SqlServerDependency:System.Web.UI.Page

{

private SqlCacheDependency dependency;

private string connectionString=

WebConfigurationManager.ConnectionStrings

["ConnectionString"].ConnectionString;

protected void Page_Load(object sender, EventArgs e)

{

if(!this.IsPostBack)

{

SqlDependency.Stop(connectionString);

SqlDependency.Start(connectionString);

lblInfo.Text+="开始创建依赖项……<br/>";

Cache.Remove("employee");

DataTable dt=GetTable();

Cache.Insert("employee",dt, dependency);

lblInfo.Text+="添加依赖项:

Cache.Insert(\"employee\",dt, dependency)<br/>";

}

}

private DataTable GetTable()

{

SqlConnection con=

new SqlConnection(connectionString);

using(con)

{

string sql="select employeename from dbo.employee";

SqlDataAdapter da=new SqlDataAdapter(sql, con);

dependency=

new SqlCacheDependency(da.SelectCommand);

DataSet ds=new DataSet();

da.Fill(ds,"employee");

return ds.Tables[0];

}

}

protected void bt_Update_Click(object sender, EventArgs e)

{

SqlConnection con=

new SqlConnection(connectionString);

using(con)

{

string sql="update dbo.employee set

employeename='mawei10'where employeeid=10";

SqlCommand cmd=new SqlCommand(sql, con);

con.Open();

cmd.ExecuteNonQuery();

}

lblInfo.Text+=

"执行bt_Update_Click事件,修改完成.<br/>";

}

protected void bt_GetData_Click(object sender, EventArgs e)

{

if(Cache["employee"]==null)

{

lblInfo.Text+="执行bt_GetData_Click事件,

Cache[\"employee\"]数据不存在.<br/>";

}

else

{

lblInfo.Text+="执行bt_GetData_Click事件,

Cache[\"employee\"]数据还存在.<br/>";

}

}

}

}


在上面的代码中,SqlDependency对象表示应用程序和SQL Server 2005实例间的查询通知依赖关系。

SqlDependency. Start方法启动用于接收依赖项更改通知的侦听器,该通知来自由连接字符串指定的SQL Server实例。如果侦听器初始化成功,则为true;如果已存在兼容的侦听器,则为false。

SqlDependency. Stop方法用于停止在上一次Start调用中指定的连接的侦听器。如果侦听器完全停止,则为true;如果AppDomain从侦听器解除绑定,但至少还有一个其他AppDomain使用同一侦听器,则为false。

示例运行结果如图19-3所示。

figure_0701_0513

图 19-3 示例运行结果

19.4.2 自定义缓存依赖

在ASP.NET中,它允许继承CacheDependency类创建自定义的缓存依赖,这和上一节所讲的SqlCacheDependency类所做的差不多。设计一个自定义的CacheDependency类很简单,要做的只是启动一个异步任务,它检查依赖项目何时发生变化。依赖项目发生变化时,将调用基方法CacheDependency.NotifyDependencyChanged。作为回应,基类更新HasChanged与UTclastModified属性值,并且ASP.NET自动从缓存中移除所有相关项目。

我们知道,现在的许多网站都提供了RSS功能,从而方便我们去订阅。因此,在应用程序里订阅这些RSS的时候,可以在缓存中放置RSS数据,显示的时候使用一个样式转换。而在检查依赖性的时候,只需要简单地比较一下当前的RSS与网站的RSS是否相同就可以了。

那么该何时去检查、比较这些RSS数据呢?其实,可以使用一个Timer来控制,让它定期去检查是否有更新,如果有更新则通知依赖发生了改变。

此外,为了便于重用,需要在自定义的缓存依赖类MyCacheDependency中定义一个url变量,用来保存要获取的RSS数据的URL。还需要定义一个时间间隔timeInterval,便于在使用的时候调整刷新速度。详细代码如代码清单19-3所示。

代码清单19-3 MyCacheDependency.cs


using System;

using System.Collections.Generic;

using System.Xml.XPath;

using System.Web;

using System.Web.Caching;

using System.Threading;

namespace_19_1

{

public class MyCacheDependency:CacheDependency

{

private Timer_timer;

private int_timeInterval;

private XPathNavigator_rss;

private string_url;

private int_pollTime=5000;

public XPathNavigator RSS

{

get

{

return_rss;

}

}

public MyCacheDependency(string url, int timeInterval)

{

_url=url;

_timeInterval=timeInterval;

_rss=GetRSS();

_timer=new Timer(

new TimerCallback(CheckDependencyCallback),

this,_timeInterval*_pollTime,

_timeInterval*_pollTime);

}

private XPathNavigator GetRSS()

{

XPathDocument doc=new XPathDocument(_url);

return doc.CreateNavigator();

}

public void CheckDependencyCallback(object sender)

{

XPathNavigator nav=GetRSS();

if(nav.OuterXml!=_rss.OuterXml)

{

base.NotifyDependencyChanged(this,

EventArgs.Empty);

_timer.Dispose();

}

}

protected override void DependencyDispose()

{

if(_timer!=null)

{

_timer.Dispose();

}

}

}

}


在MyCacheDependency类中,在CheckDependencyCallback方法里将两个RSS信息进行比较,如果不同,则调用NotifyDependencyChanged方法通知基类:相应的缓存依赖已经发生了变化,缓存中的数据应当被清除。

与此同时,在本类的最后还重写了DependencyDispose方法执行所有必需的清理工作。使用NotifyDependencyChanged方法使缓存的项目失效之后,很快就会调用DependencyDispose方法。此时,已经不再需要依赖了。

MyCacheDependency类的测试页面MyCacheDependencyWebForm.aspx如代码清单19-4所示。

代码清单19-4 MyCacheDependencyWebForm.aspx


<%@Page Language="C#"AutoEventWireup="true"

CodeBehind="MyCacheDependencyWebForm.aspx.cs"

Inherits="_19_1.MyCacheDependencyWebForm"%>

<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

<title></title>

</head>

<body>

<form id="form1"runat="server">

<div>

博客园最新帖子:

<br/>

<asp:Xml ID="RssXml"runat="server"/>

<br/>

<asp:Label ID="Label1"runat="server"ForeColor="red"/>

</div>

</form>

</body>

</html>


在页面的后台代码里,首先需要判断RSS缓存项Cache["Key"]是否存在。如果Cache["Key"]为null,则调用MyCacheDependency类来创建一个缓存项,最后将缓存项绑定到RssXml控件上。如下面的代码所示:


public partial class MyCacheDependencyWebForm:System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

string url="http://www.cnblogs.com/RSS.aspx";

if(Cache["Key"]==null)

{

MyCacheDependency dependency=

new MyCacheDependency(url,500);

Cache.Insert("Key",dependency.RSS, dependency);

Label1.Text="当前数据为刚刚获取,并已更新入缓存!";

}

else

{

Label1.Text="当前数据是从缓存中取得!";

}

RssXml.XPathNavigator=Cache["Key"]as

System.Xml.XPath.XPathNavigator;

RssXml.TransformSource="translate.xsl";

}

}


其中,translate.xsl文件代码如下所示:


<xsl:stylesheet

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"version="1.0"

xmlns:myns="http://www.comesns.com/sample">

<xsl:template match="channel">

<div style="background-color:#cccccc;font-size:12px;">

<xsl:for-each select="item">

<a>

<xsl:attribute name="href">

<xsl:value-of select="link"/>

</xsl:attribute>

<xsl:value-of select="title"/>

</a>

<br/>

</xsl:for-each>

</div>

</xsl:template>

</xsl:stylesheet>


因为在MyCacheDependencyWebForm页面中给MyCacheDependency类传入的url是http://www.cnblogs.com/RSS.aspx。所以,示例运行结果如图19-4所示。

figure_0705_0514

图 19-4 示例运行结果