一、简介
ASP.NET 1.x Cache API是一种革命性特征。当一个XML文件或另一个缓冲项的内容改变时,Cache API提供了诸如声明性输出缓冲、以编程方式控制输出缓冲以及使缓冲项无效等能力。尽管这大大改进了Web应用程序的性能,但遗憾的是,ASP.NET 1.x并没有提供一种机制来实现当数据库中的数据改变时使一个缓存对象中的数据无效。最终,在ASP.NET 2.0中加入了这一特征。此外,ASP.NET 2.0还提供了特定功能用于缓冲一个SqlDataSource控件的输出―你可以充分利用缓冲优点而不用编写一行代码。本文中,让我们共同讨论这些新的 缓冲特征,以及如何把它们应用于自己的Web应用程序。
在ASP.NET 2.0中,缓冲以多种方式加以改进。也许最引人瞩目的改进是引入了数据库触发的缓存无效机制。在ASP.NET 1.x中,你能够基于一些预定义条件(例如一个XML文件中的改变,在另一个缓存项中的改变等)来使一个缓冲项无效。借助于这一特征,当数据或另一个缓冲 项改变时,你可以删除或从缓存中使该项无效。然而,当一个SQL Server数据库中的数据改变时,ASP.NET 1.x Cache API并不允许你使缓存中的一个项成为无效的,尽管大多数应用程序都要求具有这种能力。相比之下,ASP.NET 2.0则特别重视这一点―通过提供数据库触发的缓存无效功能,它使你确保缓存中的项与数据库中的改变保持同步更新。
ASP.NET 2.0另一个重要的缓冲特征是能够在SqlDataSource级上支持缓冲。该SqlDataSource控件能够操作SQL Server、OLEDB、ODBC和Oracle数据库等多种数据库,而且支持你使用SQL命令选择、更新、删除和插入数据。现在,借助于在 SqlDataSource控件级上设置缓冲属性的能力,你可以更为细致地控制缓冲的数据。
ASP.NET 2.0还提供一个新的Substitution控件,你能够使用它来把动态的内容注入到另一个缓冲的Web页面中。如果你有一个启动了输出缓冲功能的页面 但是仍然想显示动态的内容(这需要在每次请求该页面时重新生成)的话,那么你可以考虑使用该Substitution控件。
下列几节中,我将详细分析上面的特征并提供相关的示例。
【提示】本文中提供了三个简单但完整的aspx页面源码,我使用Visual Studio 2005在一个简单web工程Caching中分别试验,并用浏览器预览这三个页面。至于如何建立工程与SQL Server 2005/2000/7.0示例数据库Northwind的连接,在此略过。
二、在SqlDataSource控件中实现基于时间的缓存无效
ASP.NET缓冲是一种能够改进Web应用程序性能的重要特征。事实上,改进一个数据库驱动的Web应用程序性能的最显著的方法正体现在对缓冲机 制的巧妙把握上。在绝大多数情况下,从数据库中检索数据成为你能够实现的最慢的网站操作之一。然而,如果你能够在内存中恰当地缓存数据库数据并且尽可能在 每一次页面请求时避免访问数据库,那么你一定能够显著地改进你的应用程序的性能。
ASP.NET 2.0提供了比ASP.NET 1.x更强的缓冲特征。其中,一个新的特征是,它能够把caching属性指定为数据源控件声明的一部分。ASP.NET 2.0中的这个新的数据源控件能够“无缝”地利用ASP.NET 2.0新的缓冲特征特征,从而使你能够把caching属性设置为SqlDataSource控件声明的一部分。
典型地,你可以通过设置SqlDataSource控件中的下列两个属性来支持缓冲:
◆EnableCaching―通过把这个属性设置为true,你可以启动一个SqlDataSource控件的缓冲功能。
◆CacheDuration―这个属性允许你设置或得到SqlDataSource控件中的缓冲数据的持续时间(这个属性以秒为单位指定)。
作为本文示例,不妨让我们考虑一下Northwind数据库中的categories和products表的情况。它在一个 DropDownList中显示所有的产品种类并在一个GridView控件中显示属于一个特定种类的产品。首先,让我们使用Visual Studio 2005创建一个新的网站,并命名为Caching。然后,把一个Web页面TimeBasedCaching.aspx添加到其中。最后,按如下所示修 改TimeBasedCaching.aspx文件中的代码:
<%@ Page Language="C#" %>
<html>
<head>
<title>SqlDataSource控件缓冲与参数分析试验</title>
</head>
<body>
<form id="form1" runat="server">
<asp:DropDownList DataValueField="CategoryID"
DataTextField="CategoryName"
DataSourceID="CategoriesDataSource"
ID="DropDownList1" Runat="server" AutoPostBack="True">
</asp:DropDownList>
<br/><br/>
<asp:GridView ID="GridView1" Runat="server"
DataSourceID="ProductsDataSource"
DataKeyNames="ProductID" AutoGenerateColumns="False">
<Columns>
<asp:BoundField HeaderText="ProductID" DataField="ProductID"
SortExpression="ProductID" />
<asp:BoundField HeaderText="Timestamp" DataField="Timestamp"
SortExpression="Timestamp" />
<asp:BoundField HeaderText="ProductName"
DataField="ProductName"
SortExpression="ProductName" />
<asp:BoundField HeaderText="QuantityPerUnit"
DataField="QuantityPerUnit"
SortExpression="QuantityPerUnit" />
<asp:BoundField HeaderText="UnitPrice" DataField="UnitPrice"
SortExpression="UnitPrice" />
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="ProductsDataSource" Runat="server"
SelectCommand="SELECT DatePart(second, GetDate())
As Timestamp, *
FROM [Products] where CategoryID = @CategoryID"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
EnableCaching="True" CacheDuration="10">
<SelectParameters>
<asp:ControlParameter Name="CategoryID"
ControlID="DropDownList1"
PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
<asp:SqlDataSource ID="CategoriesDataSource" Runat="server"
SelectCommand="SELECT * FROM [Categories]"
ConnectionString="<%$
ConnectionStrings:Northwind %>"
EnableCaching="True" CacheDuration="10"/>
</form>
</body>
</html>
|
在上面的代码中,到数据库的连接字符串是从web.config文件中检索的。这个web.config文件包含下列connectionStrings元素:
<connectionStrings>
<add name="Pubs"
connectionString="server=localhost;database=Pubs;
trusted_connection=true"/>
<add name="Northwind"
connectionString="server=localhost;database=Pubs;
trusted_connection=true"/>
</connectionStrings>
|
现在,既然要求的连接字符串已经在web.config文件中定义,那么,SqlDataSource控件就能够借助于下列声明来使用这个连接字符串:
<%$ ConnectionStrings:Northwind %>
|
上面的代码检索定义在Northwind连接字符串元素的connectionString属性中的连接字符串值。
SqlDataSource控件还要把EnableCaching属性设置为true,这样会使SqlDataSource自动地缓冲通过 SelectCommand检索的数据。这个CacheDuration属性能够使你指定(以秒为单位)在数据从数据库中刷新之前应该被缓冲多长时间。默 认情况下,SqlDataSource将使用一种绝对过期策略来缓存数据;这意味着,数据每隔在CacheDuration属性中指定的秒数刷新一次。
你还可以选择性配置让SqlDataSource使用一种“滑动式”过期策略。通过这种策略,只要数据继续被存取,它就不会被删除。当你有大量需要 被缓冲的项时,使用滑动过期策略非常有用,因为该策略能够使你在内存中仅保持被最频繁访问的项。在上面的示例中,通过分别把EnableCaching和 CacheDuration属性设置为True和10,你缓冲SQL查询结果的时间期限为10秒。
三、在SqlDataSource控件中的SQL缓存无效
到目前为止,你已看到了如何使基于在SqlDataSource控件中的CacheDuration属性中设定的持续时间值缓存无效。在本节中,我 将解释基于SQL Server表中的数据实现一个缓存无效机制的步骤。这种缓存无效机制是ASP.NET 2.0的一个新特征,这时一个缓冲页面或一个数据源控件能够被绑定到一个SQL Server数据库中的某一个特定表上。一旦你实现这种初始化的关联操作,那么,改变该表的内容将导致缓冲的对象自动无效。
【建议】当你需要在多个页面中使用相同的数据库数据时,你应该考虑使用数据源控件的SQL缓存无效机制。
其实,基于SQL Server的缓存无效机制适用于SQL Server 7.0及以上版本;但是,SQL Server 7.0和2000仅支持表级的缓存无效机制。这意味着,当任何时间表中的数据改变时,缓冲的项被自动地无效。SQL Server 2005还提供了一种行级缓存无效机制,这可以在一种更细的级别上来控制缓冲数据。
在SQL Server 7和SQL Server 2000中,表级缓存无效是通过使用一种查询系统(一个特定的ASP.NET进程)支持的。该进程每隔特定的时间查询数据库(“拉”模型)以监视自从上次 被检查以来哪些表发生了变化。尽管这种拉模型能够适合于大多数情形,但是它并不是一种非常有效的方法。幸好,这种情形在SQL Server 2005中得到了大大增强―无论何时修改一个特定的数据行,实际上都由SQL Server负责通知(推模型)ASP.NET。SQL Server 2005通过使用一个称为通知提交服务(Notification Delivery Services,它使用80端口)的特征实现这一功能,该服务能够直接与IIS6.0的HTTP.SYS进行交互以通知Web服务器更新特定的行。接下 来,我将解释如何为SQL Server 7和2000版本配置缓冲。
在你能够使用SQL Server 7或SQL Server 2000建立缓存依赖性之前,你需要完成下列步骤:
◆配置SQL Server以支持SQL缓存无效。这只需要一次性建立你想监视的SQL Server数据库中的表或数据库即可。
◆把必要的配置信息添加到web.config文件。
下面,让我们详细分析上面的步骤。首先,我们来看一下SQL Server的配置问题。
四、配置SQL Server以支持SQL缓存无效
你可以以下列两种方式实现SQL Server 2000配置以支持SQL缓存无效:
1.使用aspnet_regsql工具;
2.使用SqlCacheDependencyAdmin类的EnableTableForNotifications方法。
在本文中,我们仅考虑第一种方法。上面的aspnet_regsql工具负责创建另外一个表 AspNet_SqlCacheTablesForChangeNotification,用于跟踪改变数据库中的所有被监视的表的变化;它还创建一组触 发器和存储过程来支持这种能力。为了运行aspnet_regsql工具,只需打开Visual Studio命令提示行,然后输入类似下列的命令:
D:\>aspnet_regsql �CS localhost �CU sa �CP thiru �Cd Pubs �Ced
|
运行正常的话,应该显示如下:
Enabling the database for SQL cache dependency.
…
Finished.
D:\>
|
该命令使Pubs数据库支持SQL缓存无效功能。其中:
◆S―服务器名
◆U―用于连接到SQL Server的用户ID
◆P―用于连接到SQL Server的用户口令
◆d―数据库名
◆ed―支持该数据库具有SQL Server触发的缓存无效功能
一旦在数据库级上完成上面的操作,接下来,你需要在单个表级上启动缓存无效功能。具体实现请参考下列命令:
D:\>aspnet_regsql �CS localhost �CU sa �CP thiru �Ct authors �Cd Pubs �Cet
|
运行正常的话,应该显示如下:
Enabling the table for SQL cache dependency.
…
Finished.
D:\>
|
在上面的命令中:
◆t―指定表的名称
◆et―支持该表具有SQL Server触发的缓存无效功能
上面的命令展示了如何为Pubs数据库中的authors表启动SQL缓存无效支持。一旦你配置好authors表可以发送通知,那么,任何时候该表中的数据变化时,它都会通知ASP.NET使位于缓存中的相应的项无效。
五、为SQL缓存无效实现Web配置
下一步,在你能够把SQL缓存无效功能使用于你的ASP.NET应用程序之前,还要更新Web配置文件―你需要指示ASP.NET框架查询你已经启动SQL缓存无效功能的数据库。下列Web配置文件包含以特定的时间间隔周期性地查询Pubs数据库中必要的配置信息:
<configuration>
<connectionStrings>
<add name="Pubs"
connectionString="Server=localhost;Database=Pubs;uid=sa;
pwd=thiru" />
</connectionStrings>
<system.web>
<caching>
<sqlCacheDependency enabled="true">
<databases>
<add name="Pubs"
connectionStringName="Pubs"
pollTime="60000" />
</databases>
</sqlCacheDependency>
</caching>
</system.web>
</configuration>
|
前面的Web配置文件包含两个节―<connectionStrings>和<caching>。其中, connectionStrings节创建一个名为“Pubs”的到Pubs数据库的数据库连接字符串;caching节实现配置SQL缓存无效查询;在 databases子节下,列出一个或多个你想查询其变化情况的数据库;位于databases节中的add节显示Pubs数据库每一分钟(每60, 000毫秒)被查询一次。你可以为不同的数据库指定不同的查询间隔时间。记住,每当查询数据库变化情况时,服务器都必须多做一些工作。如果你不希望数据库 中的数据经常改变的话,那么,你应该增加查询间隔时间。
六、在SqlDataSource控件中实现SQL缓存无效
现在,既然你已经实现了要求的配置,那么接下来,你就可以在你的ASP.NET Web页面中利用SQL缓存无效特征。为此,只需要把一个新的Web表单SqlCacheInvalidation.aspx添加到你的网站上。下列是相 应于SqlCacheInvalidation.aspx的代码:
<%@ Page Language="C#" %>
<html>
<head>
<title>SQL缓存无效示例</title>
</head>
<body>
<form id="form1" runat="server">
<asp:GridView ID="GridView1" Runat="server"
DataSourceID="SqlDataSource1" DataKeyNames="au_id"
AutoGenerateColumns="False">
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:BoundField ReadOnly="True" HeaderText="timestamp"
DataField="timestamp" SortExpression="timestamp" />
<asp:BoundField ReadOnly="True" HeaderText="au_id"
DataField="au_id" SortExpression="au_id" />
<asp:BoundField HeaderText="au_lname" DataField="au_lname"
SortExpression="au_lname" />
<asp:BoundField HeaderText="au_fname" DataField="au_fname"
SortExpression="au_fname" />
<asp:BoundField HeaderText="phone" DataField="phone"
SortExpression="phone" />
<asp:BoundField HeaderText="address" DataField="address"
SortExpression="address" />
<asp:BoundField HeaderText="city" DataField="city"
SortExpression="city" />
<asp:BoundField HeaderText="state" DataField="state"
SortExpression="state" />
<asp:BoundField HeaderText="zip" DataField="zip"
SortExpression="zip" />
<asp:CheckBoxField HeaderText="contract"
SortExpression="contract"
DataField="contract" />
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
SelectCommand="SELECT DatePart(second, GetDate())
As timestamp, * FROM [authors]" ConnectionString="<%$
ConnectionStrings:Pubs %>" EnableCaching="True"
DeleteCommand="DELETE FROM [authors]
WHERE [au_id] = @original_au_id"
InsertCommand="INSERT INTO [authors] ([au_id], [au_lname],
[au_fname], [phone], [address], [city], [state], [zip],
[contract]) VALUES (@au_id, @au_lname, @au_fname, @phone,
@address, @city, @state, @zip, @contract)"
UpdateCommand="UPDATE [authors] SET [au_lname] = @au_lname,
[au_fname] = @au_fname, [phone] = @phone,
[address] = @address, [city] = @city, [state] = @state,
[zip] = @zip, [contract] = @contract
WHERE [au_id] = @original_au_id"
SqlCacheDependency="Pubs:authors">
<DeleteParameters>
<asp:Parameter Name="original_au_id" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Type="String" Name="au_lname" />
<asp:Parameter Type="String" Name="au_fname" />
<asp:Parameter Type="String" Name="phone" />
<asp:Parameter Type="String" Name="address" />
<asp:Parameter Type="String" Name="city" />
<asp:Parameter Type="String" Name="state" />
<asp:Parameter Type="String" Name="zip" />
<asp:Parameter Type="Boolean" Name="contract" />
<asp:Parameter Name="original_au_id" />
</UpdateParameters>
<InsertParameters>
<asp:Parameter Type="String" Name="au_id" />
<asp:Parameter Type="String" Name="au_lname" />
<asp:Parameter Type="String" Name="au_fname" />
<asp:Parameter Type="String" Name="phone" />
<asp:Parameter Type="String" Name="address" />
<asp:Parameter Type="String" Name="city" />
<asp:Parameter Type="String" Name="state" />
<asp:Parameter Type="String" Name="zip" />
<asp:Parameter Type="Boolean" Name="contract" />
</InsertParameters>
</asp:SqlDataSource>
</form>
</body>
</html>
|
上面的代码在SqlDataSource控件上施加了SQL缓存无效功能。你已经看到,这个SqlDataSource控件包含EnableCaching和SqlCacheDependency两个属性。其中,SqlCacheDependency属性使用下列语法:
SqlCacheDependency="Pubs:authors"
|
在这个属性声明中,你首先列举出数据库的名称,后面跟着的是数据库表名。由于这一属性,无论何时Pubs数据库中的authors表中的数据改变, 缓冲数据都会自动地无效。注意,你在此指定的数据库名应该已经定义在web.config文件的connectionStrings节中。
作为SqlDataSource控件声明的一部分,你还要指定SelectCommand,InsertCommand, DeleteCommand,UpdateCommand和ConnectionString属性。这些xxxCommand属性允许你指定要被执行的 SQL命令,而ConnectionString属性允许你指定用于连接到Pubs数据库的连接字符串;另外,这个值还可以从web.config文件中 进行检索。因为该xxxCommand属性也包含SQL查询参数,所以,它们可以在DeleteParameters,UpdateParameters 和InsertParameters模板中指定。
最后,声明一个GridView控件并且使用DataSourceID属性把GridView控件绑定到SqlDataSource控件上。通过把 GridView的DataSourceID属性设置为SqlDataSource控件的ID,从SqlDataSource返回的数据就可以被自动地显 示。
现在,代码分析完毕;那么,接下来,你可以启动浏览器导航到该页面来测试这种缓冲功能。你将看到类似下图1的输出结果:
图1:在SqlDataSource控件中实现SQL缓存无效运行结果
其中,这里的timestamp栏显示数据库日期的“秒”部分。如果你再次刷新该页面,你将在随后显示的输出中看到相同的时间,因为你已启动了该SqlDataSource控件中的缓冲功能。
现在,为了测试这种基于SQL Server的触发器无效效果,你可以点击一个Edit超级链接并更新相应的作者信息。之后,你应该在显示于上面的页面中的timestamp中看到发生 了改变。这个例子非常清晰地展示了,一旦authors表中的数据发生变化,你的基于SQL Server的触发器无效机制将自动地使SqlDataSource控件中的缓冲内容无效。
七、部分页面缓冲技术
到目前为止,你已经看到了如何在SqlDataSource控件中使用缓冲技术。本节将向你展示如何使用回寄方式缓存Substitution以便 用刷新的内容替换被缓冲的内容部分。这称作“部分页面缓冲”或“页面片断缓冲”。这是一种强有力的特征,它允许应用程序使用页面级缓冲―即使该页面的部分 是动态生成的。
页面片断缓冲提供对页面中一个片断进行缓冲的功能,这与缓冲整个页面形成对照。有时,整页面输出缓冲是不可行的―例如,当该页面的部分需要针对每一 个用户请求动态地创建时。在这些情况下,标识出该页面或控件中不经常改变的部分以及那些需要耗费相应的服务器时间资源才能创建的部分可能是非常有价值的。 在你标识这些部分后,你就能够把它们包装到一个Web表单用户控件中并且缓存该控件;这样以来,页面中的这些部分就不需要每次被重建。这是在 ASP.NET 2.0以前实现页面片断缓冲的唯一方法。借助于ASP.NET 2.0中的新的回寄缓存Substitution特征,在页面呈现给用户之前,你可以通知ASP.NET运行时刻重新计算一个特定元素(正当它被生成到一 个缓冲页面上时)。为此,你可以使用两种方案来实现这一目的:
◆调用新的Response.WriteSubstitution方法,同时传递一个到Substitution回调函数的引用。
◆把一个<asp:substitution>控件添加到Web页面并且把MethodName属性设置为该回调函数名。
无论使用哪种方案,你都需要在该页面的顶部放一条@Output缓存指令以指定相应的持续时间,而且该依赖性的其它的参数也应该被添加到该页面上。在这个示例中,我们选用Substitution控件。
该Substitution控件具有一个重要的属性―MethodName,它用于指定被调用以提供动态内容的方法。注意,Substitution控件调用的方法必须是一个静态方法。
而且,该方法必须有一个代表当前HttpContext的参数。为了说明这个问题,我们创建一个新的Web表单PartialPageCaching.aspx并把它添加到网站上。下面是示例PartialPageCaching.aspx相应的实现代码:
<%@ Page Language="C#" %>
<%@ OutputCache Duration="6000" VaryByParam="none" %>
<script runat="server">
public static string GetRandomNumber(HttpContext context)
{
int randomNumber;
randomNumber = new System.Random().Next(1, 10000);
return randomNumber.ToString();
}
</script>
<html>
<head>
<title>Partial Page Caching using Substitution control</title>
</head>
<body>
<form id="form1" runat="server">
The random number generated is:
<asp:Substitution ID="Substitution1" MethodName="GetRandomNumber"
Runat="Server"/>
<p>
The current time is
<%= DateTime.Now.ToString("t") %>. It never changes since the
page is cached.
</p>
</form>
</body>
</html>
|
在该页面的顶部,Output缓存指令在内存中缓冲该页面的内容。在Output缓存指令中,你把Duration属性设置为6,000毫秒。 VaryByParam属性显示,在进行缓冲时ASP.NET是否应该考虑这些页面参数。当把VaryByParam设置为none时,却意味着不考虑任 何参数;所有的用户将接收相同的页面而不考虑提供的其它参数(参考下图2的输出结果)。
图2:部分页面缓冲运行效果图
Substitution控件的MethodName属性被设置为一个方法GetRandomNumber,它简单地返回一个在1~10,000之 间的随机数字。当你向该页面发出一个请求时,被显示的当前时间总是保持不变,而被该Substitution控件生成的页面部分却每次都保持改变。在这种 情况下,在每当有人请求该页面时,它显示一个在1~10,000之间的随机数字。
八、结论
ASP.NET 2.0提供的缓存API基于ASP.NET 1.0基础之上,并且有助于我们实现构建高性能的ASP.NET应用程序的理想。ASP.NET 2.0提供的新的数据源控件中提供了使在内存中缓冲数据库数据非常容易的属性。借助于这些控件,你可以检索并缓冲数据库数据而不需编写一行代码。当数据库 中的数据改变时能够无效一个缓冲项,这是微软开发人员经过长期探索才找到的一种革命化ASP.NET应用程序构建和发布的方式。最后,新的 Substitution控件有助于使你很容易地把动态的内容注入到一个缓冲页面中。
本文出自 “青峰” 博客,转载请与作者联系!