开发人员都喜欢 ASP.NET 应用程序缓存。 一个原因是 ASP.NET 能够在放入缓存中的项与文件系统中的文件之间创建相关性。 如果相关性所针对的文件更改,ASP.NET 会自动将相关项从缓存中删除。 通过与缓存删除回叫(当缓存项删除时向所有关注方广播通知)结合,缓存相关性为开发人员提供了方便,使他们得以通过尽量减少耗时的文件访问来最大限度地提高性能,因为这使他们可以放心地允许文件数据缓存,而不必担心数据变得陈旧。
尽管缓存相关性非常实用,但是在 ASP.NET 1.0 中还缺乏一项至关重要的功能,这项功能一旦存在,将会使缓存相关性随着开发人员的美梦成真而得到证明。 这项功能就是对数据库实体的支持。 在现实情况中,大多数 Web 应用程序都是从数据库中提取数据,而不是从文件中提取数据。 尽管 ASP.NET 能够非常出色地将缓存项链接到文件,但无法将缓存项链接到数据库实体。 也就是说,可以将文件内容读入 DataSet 中,然后缓存 DataSet,并使 DataSet 在初始化时所在的文件更改时自动从缓存中删除。 但是,无法使用数据库查询来初始化 DataSet,因此也就无法缓存 DataSet 并使 DataSet 自动在数据库更改时废弃。 这确实太糟糕了,因为正是由于数据库访问过多(例如,文件 I/O 过多)而导致应用程序性能下降。
ASP.NET 不支持数据库相关性并不意味着数据库相关性不可能实现。 这一部分 Wicked Code 展示了扩展 ASP.NET 应用程序缓存以支持数据库相关性的技术。 它包含数据库触发器及扩展存储过程。 尽管这里展示的实现仅适用于 Microsoft? SQL Server?,但是大体技术也适用于支持与文件系统交互的触发器和用户定义过程的任何数据库。
活动的数据库相关性
先看一个演示。
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<body>
<h3><asp:Label ID="Quotation" RunAt="server" /></h3>
<i><asp:Label ID="Author" RunAt="server" /></i>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
SqlDataAdapter adapter = new SqlDataAdapter (
"SELECT * FROM Quotations",
"server=localhost;database=quotes;uid=sa;pwd="
);
DataSet ds = new DataSet ();
adapter.Fill (ds, "Quotations");
DataTable table = ds.Tables["Quotations"];
Random rand = new Random ();
int index = rand.Next (0, table.Rows.Count);
DataRow row = table.Rows[index];
Quotation.Text = (string) row["Quotation"];
Author.Text = (string) row["Author"];
}
</script>
上面代码包含 ASP.NET 页的源代码,该 ASP.NET 页显示从名为 Quotes 的 SQL Server 数据库中随机选择的语录。 要创建数据库,请运行安装脚本。
CREATE DATABASE Quotes
GO
USE Quotes
GO
CREATE TABLE Quotations (
Quotation varchar(256) NOT NULL,
Author varchar(64) NOT NULL
)
GO
INSERT INTO Quotations (Quotation, Author) VALUES (
'Give me chastity and continence, but not yet.',
'Saint Augustine'
)
•••
INSERT INTO Quotations (Quotation, Author) VALUES (
'640K ought to be enough for anybody.',
'William Gates III'
)
GO
上面代码显示的是缩略形式的安装脚本。 完整的脚本包含在本专栏附带的可下载 zip 文件中。 (可以在 SQL Server 查询分析器内部执行该脚本,或者使用 OSQL 命令从命令行执行该脚本。) 每次提取该页时,Page_Load 都使用数据库的 Quotations 表中的所有记录初始化一个 DataSet,然后从该 DataSet 中随机选择一个记录,并将其写入到该页中。 按 F5 若干次,您将看到出自某些著名(以及不那么有名的)人士的各种随机语录,如图 3 所示。
图 3 随机语录
由于某一原因,该页被命名为 DumbDBQuotes.aspx。 每次请求该页时都查询数据库并不是很明智的做法。 在每一次访问该页时都访问数据库(尤其是当数据库是由远程服务器承载时)是生成不具可伸缩性的数据库的可靠方法。
ASP.NET 应用程序缓存可解决数据库访问过多的问题。 如果 DataSet 已缓存,便可以直接从内存(也就是缓存)中提取它,从而避免了多余的数据库访问。 DataSet 的缓存非常容易。应用程序缓存接受从 System.Object 派生的任何类型的实例。在 Microsoft .NET Framework 中,这意味着任何托管类型(包括 DataSet)的实例。 问题在于,如果在 DataSet 缓存之后,它底层的数据库更改,您为用户提供的将是陈旧的数据。 可以实现一个定期重新查询数据库的解决方案,但是理想的解决方案应该满足以下条件:不需要轮询,并且在数据源中的已更新数据一旦变得可用时便立即将其交付。
请看
<%@ Import NameSpace="System.Data" %>
<%@ Import NameSpace="System.Data.SqlClient" %>
<script language="C#" runat="server">
static Cache _cache = null;
void Application_Start ()
{
_cache = Context.Cache; // Save reference for later
//
// Query the database and cache the resulting DataSet.
//
RefreshCache (null, null, 0);
}
static void RefreshCache (string key, object item,
CacheItemRemovedReason reason)
{
//
// Query the database.
//
SqlDataAdapter adapter = new SqlDataAdapter (
"SELECT * FROM Quotations",
"server=localhost;database=quotes;uid=sa;pwd="
);
DataSet ds = new DataSet ();
adapter.Fill (ds, "Quotations");
//
// Add the DataSet to the application cache.
//
_cache.Insert (
"Quotes",
ds,
new CacheDependency ("C:\\AspNetSql\\Quotes.Quotations"),
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
new CacheItemRemovedCallback (RefreshCache)
);
}
</script>
和
<%@ Import Namespace="System.Data" %>
<html>
<body>
<h3><asp:Label ID="Quotation" RunAt="server" /></h3>
<i><asp:Label ID="Author" RunAt="server" /></i>
</body>
</html>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
DataSet ds = (DataSet) Cache["Quotes"];
if (ds != null) {
// Display a randomly selected quotation
DataTable table = ds.Tables["Quotations"];
Random rand = new Random ();
int index = rand.Next (0, table.Rows.Count);
DataRow row = table.Rows[index];
Quotation.Text = (string) row["Quotation"];
Author.Text = (string) row["Author"];
}
else {
// If quotes is null, this request arrived after the
// DataSet was removed from the cache and before a new
// DataSet was inserted. Tell the user the server is
// busy; a page refresh should solve the problem.
Quotation.Text = "Server busy";
}
}
</script>
这两个图中包含更智能的语录应用程序的源代码。 SmartDBQuotes.aspx 不从数据库中检索语录,而是从应用程序缓存中获取语录。 Global.asax 填充缓存,并在数据库更改时刷新缓存。
下面是这两段源代码的试用说明:
• 在 Web 服务器的 C: 驱动器的根目录中创建一个名为 AspNetSql 的子目录。 在 AspNetSql 内部,创建一个名为 Quotes.Quotations 的零字节文件。 确保 Everyone,或者至少 SYSTEM 和 ASPNET(安装 ASP.NET 时创建的特殊帐户)有权访问 Quotes.Quotations。
• 将包含在本专栏的可下载代码示例中的 XSP.dll 复制到 SQL Server 的 binn 目录(例如,C:\Program Files\Microsoft SQL Server\MSSQL\Binn)中,或者复制到 Web 服务器上 Windows 将在其中自动搜索 DLL 的任意位置(例如,C:\Windows\System32)。
USE master
EXEC sp_addextendedproc 'xsp_UpdateSignalFile', 'XSP.dll'
GRANT EXECUTE ON xsp_UpdateSignalFile TO PUBLIC
GO
CREATE DATABASE Quotes
GO
USE Quotes
GO
CREATE TABLE Quotations (
Quotation varchar(256) NOT NULL,
Author varchar(64) NOT NULL
)
GO
INSERT INTO Quotations (Quotati