PetShop之ASP.NET缓存(转载)

《解剖PetShop》系列之四

四 PetShop之ASP.NET缓存

如果对微型计算机硬件系统有足够的了解,那么我们对于Cache这个名词一定是耳熟能详的。在CPU以及主板的芯片中,都引入了这种名为高速缓冲存 储器(Cache)的技术。因为Cache的存取速度比内存快,因而引入Cache能够有效的解决CPU与内存之间的速度不匹配问题。硬件系统可以利用 Cache存储CPU访问概率高的那些数据,当CPU需要访问这些数据时,可以直接从Cache中读取,而不必访问存取速度相对较慢的内存,从而提高了 CPU的工作效率。软件设计借鉴了硬件设计中引入缓存的机制以改善整个系统的性能,尤其是对于一个数据库驱动的Web应用程序而言,缓存的利用是不可或缺 的,毕竟,数据库查询可能是整个Web站点中调用最频繁但同时又是执行最缓慢的操作之一,我们不能被它老迈的双腿拖缓我们前进的征程。缓存机制正是解决这 一缺陷的加速器。

4.1  ASP.NET缓存概述

作为.Net框架下开发Web应用程序的主打产品,ASP.NET充分考虑了缓存机制。通过某种方法,将系统需要的数据对象、Web页面存储在内存 中,使得Web站点在需要获取这些数据时,不需要经过繁琐的数据库连接、查询和复杂的逻辑运算,就可以“触手可及”,如“探囊取物”般容易而快速,从而提 高整个Web系统的性能。

ASP.NET提供了两种基本的缓存机制来提供缓存功能。一种是应用程序缓存,它允许开发者将程序生成的数据或报表业务对象放入缓存中。另外一种缓存机制是页输出缓存,利用它,可以直接获取存放在缓存中的页面,而不需要经过繁杂的对该页面的再次处理。

应用程序缓存其实现原理说来平淡无奇,仅仅是通过ASP.NET管理内存中的缓存空间。放入缓存中的应用程序数据对象,以键/值对的方式存储,这便于用户在访问缓存中的数据项时,可以根据key值判断该项是否存在缓存中。

放入在缓存中的数据对象其生命周期是受到限制的,即使在整个应用程序的生命周期里,也不能保证该数据对象一直有效。ASP.NET可以对应用程序缓 存进行管理,例如当数据项无效、过期或内存不足时移除它们。此外,调用者还可以通过CacheItemRemovedCallback委托,定义回调方法 使得数据项被移除时能够通知用户。

在.Net Framework中,应用程序缓存通过System.Web.Caching.Cache类实现。它是一个密封类,不能被继承。对于每一个应用程序域, 都要创建一个Cache类的实例,其生命周期与应用程序域的生命周期保持一致。我们可以利用Add或Insert方法,将数据项添加到应用程序缓存中,如 下所示:
Cache["First"] = "First Item";
Cache.Insert("Second", "Second Item");

我们还可以为应用程序缓存添加依赖项,使得依赖项发生更改时,该数据项能够从缓存中移除:
string[] dependencies = {"Second"};
Cache.Insert("Third", "Third Item",
new System.Web.Caching.CacheDependency(null, dependencies));

与之对应的是缓存中数据项的移除。前面提到ASP.NET可以自动管理缓存中项的移除,但我们也可以通过代码编写的方式显式的移除相关的数据项:
Cache.Remove("First");

相对于应用程序缓存而言,页输出缓存的应用更为广泛。它可以通过内存将处理后的ASP.NET页面存储起来,当客户端再一次访问该页面时,可以省去 页面处理的过程,从而提高页面访问的性能,以及Web服务器的吞吐量。例如,在一个电子商务网站里,用户需要经常查询商品信息,这个过程会涉及到数据库访 问以及搜索条件的匹配,在数据量较大的情况下,如此的搜索过程是较为耗时的。此时,利用页输出缓存就可以将第一次搜索得到的查询结果页存储在缓存中。当用 户第二次查询时,就可以省去数据查询的过程,减少页面的响应时间。

页输出缓存分为整页缓存和部分页缓存。我们可以通过@OutputCache指令完成对Web页面的输出缓存。它主要包含两个参 数:Duration和VaryByParam。Duration参数用于设置页面或控件进行缓存的时间,其单位为秒。如下的设置表示缓存在60秒内有 效:
<%@ OutputCache Duration=“60“ VaryByParam=“none“ %>

只要没有超过Duration设置的期限值,当用户访问相同的页面或控件时,就可以直接在缓存中获取。
使用VaryByParam参数可以根据设置的参数值建立不同的缓存。例如在一个输出天气预报结果的页面中,如果需要为一个ID为txtCity的TextBox控件建立缓存,其值将显示某城市的气温,那么我们可以进行如下的设置:
<%@ OutputCache Duration=”60” VaryByParam=”txtCity” %>

如此一来,ASP.NET会对txtCity控件的值进行判断,只有输入的值与缓存值相同,才从缓存中取出相应的值。这就有效地避免了因为值的不同而导致输出错误的数据。

利用缓存的机制对性能的提升非常明显。通过ACT(Application Center Test)的测试,可以发现设置缓存后执行的性能比未设置缓存时的性能足足提高三倍多。

引入缓存看来是提高性能的“完美”解决方案,然而“金无足赤,人无完人”,缓存机制也有缺点,那就是数据过期的问题。一旦应用程序数据或者页面结果 值发生的改变,那么在缓存有效期范围内,你所获得的结果将是过期的、不准确的数据。我们可以想一想股票系统利用缓存所带来的灾难,当你利用错误过期的数据 去分析股市的风云变幻时,你会发现获得的结果真可以说是“失之毫厘,谬以千里”,看似大好的局面就会像美丽的泡沫一样,用针一戳,转眼就消失得无影无踪。

那么我们是否应该为了追求高性能,而不顾所谓“数据过期”所带来的隐患呢?显然,在类似于股票系统这种数据更新频繁的特定场景下,数据过期的糟糕表 现甚至比低效的性能更让人难以接受。故而,我们需要在性能与数据正确性间作出权衡。所幸的是,.Net Framework 2.0引入了一种新的缓存机制,它为我们的“鱼与熊掌兼得”带来了技术上的可行性。

.Net 2.0引入的自定义缓存依赖项,特别是基于MS-SQL Server的SqlCacheDependency特性,使得我们可以避免“数据过期”的问题,它能够根据数据库中相应数据的变化,通知缓存,并移除那 些过期的数据。事实上,在PetShop 4.0中,就充分地利用了SqlCacheDependency特性。

4.2 SqlCacheDependency特性

SqlCacheDependency特性实际上是通过System.Web.Caching.SqlCacheDependency类来体现的。 通过该类,可以在所有支持的SQL Server版本(7.0,2000,2005)上监视特定的SQL Server数据库表,并创建依赖于该表以及表中数据行的缓存项。当数据表或表中特定行的数据发生更改时,具有依赖项的数据项就会失效,并自动从 Cache中删除该项,从而保证了缓存中不再保留过期的数据。
由于版本的原因,SQL Server 2005完全支持SqlCacheDependency特性,但对于SQL Server 7.0和SQL Server 2000而言,就没有如此幸运了。毕竟这些产品出现在.Net Framework 2.0之前,因此它并没有实现自动监视数据表数据变化,通知ASP.NET的功能。解决的办法就是利用轮询机制,通过ASP.NET进程内的一个线程以指 定的时间间隔轮询SQL Server数据库,以跟踪数据的变化情况。

要使得7.0或者2000版本的SQL Server支持SqlCacheDependency特性,需要对数据库服务器执行相关的配置步骤。有两种方法配置SQL Server:使用aspnet_regsql命令行工具,或者使用SqlCacheDependencyAdmin类。

4.2.1  利用aspnet_regsql工具

aspnet_regsql工具位于Windows\Microsoft.NET\Framework\[版本]文件夹中。如果直接双击该工具的执行文件,会弹出一个向导对话框,提示我们完成相应的操作:

4-1.gif
图4-1 aspnet_regsql工具

如图4-1所示中的提示信息,说明该向导主要用于配置SQL Server数据库,如membership,profiles等信息,如果要配置SqlCacheDependency,则需要以命令行的方式执行。以 PetShop 4.0为例,数据库名为MSPetShop4,则命令为:
aspnet_regsql -S localhost -E -d MSPetShop4 -ed

以下是该工具的命令参数说明:
-?  显示该工具的帮助功能;
-S  后接的参数为数据库服务器的名称或者IP地址;
-U  后接的参数为数据库的登陆用户名;
-P  后接的参数为数据库的登陆密码;
-E  当使用windows集成验证时,使用该功能;
-d  后接参数为对哪一个数据库采用SqlCacheDependency功能;
-t  后接参数为对哪一个表采用SqlCacheDependency功能;
-ed  允许对数据库使用SqlCacheDependency功能;
-dd  禁止对数据库采用SqlCacheDependency功能;
-et  允许对数据表采用SqlCacheDependency功能;
-dt  禁止对数据表采用SqlCacheDependency功能;
-lt  列出当前数据库中有哪些表已经采用sqlcachedependency功能。

以上面的命令为例,说明将对名为MSPetShop4的数据库采用SqlCacheDependency功能,且SQL Server采用了windows集成验证方式。我们还可以对相关的数据表执行aspnet_regsql命令,如:
aspnet_regsql -S localhost -E -d MSPetShop4 -t Item -et
aspnet_regsql -S localhost -E -d MSPetShop4 -t Product -et
aspnet_regsql -S localhost -E -d MSPetShop4 -t Category -et

当执行上述的四条命令后,aspnet_regsql工具会在MSPetShop4数据库中建立一个名为 AspNet_SqlCacheTablesForChangeNotification的新数据库表。该数据表包含三个字段。字段tableName记 录要追踪的数据表的名称,例如在PetShop 4.0中,要记录的数据表就包括Category、Item和Product。notificationCreated字段记录开始追踪的时间。 changeId作为一个类型为int的字段,用于记录数据表数据发生变化的次数。如图4-2所示:

4-2.gif
图4-2 AspNet_SqlCacheTablesForChangeNotification数据表

除此之外,执行该命令还会为MSPetShop4数据库添加一组存储过程,为ASP.NET提供查询追踪的数据表的情况,同时还将为使用了 SqlCacheDependency的表添加触发器,分别对应Insert、Update、Delete等与数据更改相关的操作。例如Product数 据表的触发器:
CREATE TRIGGER dbo.[Product_AspNet_SqlCacheNotification_Trigger] ON [Product]
    FOR INSERT, UPDATE, DELETE AS BEGIN
    SET NOCOUNT ON
    EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N'Product'
END

其中,AspNet_SqlCacheUpdateChangeIdStoredProcedure即是工具添加的一组存储过程中的一个。当对 Product数据表执行Insert、Update或Delete等操作时,就会激活触发器,然后执行 AspNet_SqlCacheUpdateChangeIdStoredProcedure存储过程。其执行的过程就是修改 AspNet_SqlCacheTablesForChangeNotification数据表的changeId字段值:
CREATE PROCEDURE dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure
             @tableName NVARCHAR(450)
         AS
         BEGIN
             UPDATE dbo.AspNet_SqlCacheTablesForChangeNotification WITH (ROWLOCK) SET changeId = changeId + 1
             WHERE tableName = @tableName
         END  
GO

4.2.2  利用SqlCacheDependencyAdmin类

我们也可以利用编程的方式来来管理数据库对SqlCacheDependency特性的使用。该类包含了五个重要的方法:

DisableNotifications
为特定数据库禁用 SqlCacheDependency对象更改通知
DisableTableForNotifications
为数据库中的特定表禁用SqlCacheDependency对象更改通知
EnableNotifications
为特定数据库启用SqlCacheDependency对象更改通知
EnableTableForNotifications
为数据库中的特定表启用SqlCacheDependency对象更改通知
GetTablesEnabledForNotifications
返回启用了SqlCacheDependency对象更改通知的所有表的列表

表4-1 SqlCacheDependencyAdmin类的主要方法

假设我们定义了如下的数据库连接字符串:
const string connectionStr = "Server=localhost;Database=MSPetShop4";

那么为数据库MSPetShop4启用SqlCacheDependency对象更改通知的实现为:
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
   {
       SqlCacheDependencyAdmin.EnableNotifications(connectionStr);
   }
}

为数据表Product启用SqlCacheDependency对象更改通知的实现则为:
SqlCacheDependencyAdmin.EnableTableForNotifications(connectionStr, "Product");

如果要调用表4-1中所示的相关方法,需要注意的是访问SQL Server数据库的帐户必须具有创建表和存储过程的权限。如果要调用EnableTableForNotifications方法,还需要具有在该表上创建SQL Server触发器的权限。

虽然说编程方式赋予了程序员更大的灵活性,但aspnet_regsql工具却提供了更简单的方法实现对SqlCacheDependency的配 置与管理。PetShop 4.0采用的正是aspnet_regsql工具的办法,它编写了一个文件名为InstallDatabases.cmd的批处理文件,其中包含了对 aspnet_regsql工具的执行,并通过安装程序去调用该文件,实现对SQL Server的配置。

4.3 在PetShop 4.0中ASP.NET缓存的实现

PetShop作为一个B2C的宠物网上商店,需要充分考虑访客的用户体验,如果因为数据量大而导致Web服务器的响应不及时,页面和查询数据迟迟 得不到结果,会因此而破坏客户访问网站的心情,在耗尽耐心的等待后,可能会失去这一部分客户。无疑,这是非常糟糕的结果。因而在对其进行体系架构设计时, 整个系统的性能就显得殊为重要。然而,我们不能因噎废食,因为专注于性能而忽略数据的正确性。在PetShop 3.0版本以及之前的版本,因为ASP.NET缓存的局限性,这一问题并没有得到很好的解决。PetShop 4.0则引入了SqlCacheDependency特性,使得系统对缓存的处理较之以前大为改观。

4.3.1  CacheDependency接口

PetShop 4.0引入了SqlCacheDependency特性,对Category、Product和Item数据表对应的缓存实施了SQL Cache Invalidation技术。当对应的数据表数据发生更改后,该技术能够将相关项从缓存中移除。实现这一技术的核心是 SqlCacheDependency类,它继承了CacheDependency类。然而为了保证整个架构的可扩展性,我们也允许设计者建立自定义的 CacheDependency类,用以扩展缓存依赖。这就有必要为CacheDependency建立抽象接口,并在web.config文件中进行配 置。

在PetShop 4.0的命名空间PetShop.ICacheDependency中,定义了名为IPetShopCacheDependency接口,它仅包含了一个接口方法:
public interface IPetShopCacheDependency
{      
    AggregateCacheDependency GetDependency();
}

AggregateCacheDependency是.Net Framework 2.0新增的一个类,它负责监视依赖项对象的集合。当这个集合中的任意一个依赖项对象发生改变时,该依赖项对象对应的缓存对象都将被自动移除。
AggregateCacheDependency 类起到了组合CacheDependency对象的作用,它可以将多个CacheDependency对象甚至于不同类型的 CacheDependency对象与缓存项建立关联。由于PetShop需要为Category、Product和Item数据表建立依赖项,因而 IPetShopCacheDependency的接口方法GetDependency()其目的就是返回建立了这些依赖项的 AggregateCacheDependency对象。

4.3.2  CacheDependency实现

CacheDependency的实现正是为Category、Product和Item数据表建立了对应的SqlCacheDependency类型的依赖项,如代码所示:
public abstract class TableDependency : IPetShopCacheDependency
{
    // This is the separator that's used in web.config
    protected char[] configurationSeparator = new char[] { ',' };

    protected AggregateCacheDependency dependency = new AggregateCacheDependency();
    protected TableDependency(string configKey)
    {
        string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
        string tableConfig = ConfigurationManager.AppSettings[configKey];
        string[] tables = tableConfig.Split(configurationSeparator);

        foreach (string tableName in tables)
            dependency.Add(new SqlCacheDependency(dbName, tableName));
    }
    public AggregateCacheDependency GetDependency()
   {
        return dependency;
    }
}

需要建立依赖项的数据库与数据表都配置在web.config文件中,其设置如下:
<add key="CacheDatabaseName" value="MSPetShop4"/>
<add key="CategoryTableDependency" value="Category"/>
<add key="ProductTableDependency" value="Product,Category"/>
<add key="ItemTableDependency" value="Product,Category,Item"/>

根据各个数据表间的依赖关系,因而不同的数据表需要建立的依赖项也是不相同的,从配置文件中的value值可以看出。然而不管建立依赖项的多寡,其 创建的行为逻辑都是相似的,因而在设计时,抽象了一个共同的类TableDependency,并通过建立带参数的构造函数,完成对依赖项的建立。由于接 口方法GetDependency()的实现中,返回的对象dependency是在受保护的构造函数创建的,因此这里的实现方式也可以看作是 Template Method模式的灵活运用。例如TableDependency的子类Product,就是利用父类的构造函数建立了Product、Category 数据表的SqlCacheDependency依赖:
public class Product : TableDependency
{
    public Product() : base("ProductTableDependency") { }
}

如果需要自定义CacheDependency,那么创建依赖项的方式又有不同。然而不管是创建SqlCacheDependency对象,还是自 定义的CacheDependency对象,都是将这些依赖项添加到AggregateCacheDependency类中,因而我们也可以为自定义 CacheDependency建立专门的类,只要实现IPetShopCacheDependency接口即可。

4.3.3  CacheDependency工厂

继承了抽象类TableDependency的Product、Category和Item类均需要在调用时创建各自的对象。由于它们的父类 TableDependency实现了接口IPetShopCacheDependency,因而它们也间接实现了 IPetShopCacheDependency接口,这为实现工厂模式提供了前提。

在PetShop 4.0中,依然利用了配置文件和反射技术来实现工厂模式。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为创建IPetShopCacheDependency对象的工厂类:
public static class DependencyAccess
{       
    public static IPetShopCacheDependency CreateCategoryDependency()
    {
        return LoadInstance("Category");
    }
    public static IPetShopCacheDependency CreateProductDependency()
    {
        return LoadInstance("Product");
    }
    public static IPetShopCacheDependency CreateItemDependency()
    {
        return LoadInstance("Item");
    }
    private static IPetShopCacheDependency LoadInstance(string className)
    {
        string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
        string fullyQualifiedClass = path + "." + className;
        return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
    }
}
整个工厂模式的实现如图4-3所示:

4-3.gif
 图4-3 CacheDependency工厂

虽然DependencyAccess类创建了实现了IPetShopCacheDependency接口的类Category、Product、 Item,然而我们之所以引入IPetShopCacheDependency接口,其目的就在于获得创建了依赖项的 AggregateCacheDependency类型的对象。我们可以调用对象的接口方法GetDependency(),如下所示:
AggregateCacheDependency dependency = DependencyAccess.CreateCategoryDependency().GetDependency();

为了方便调用者,似乎我们可以对DependencyAccess类进行改进,将原有的CreateCategoryDependency()方法,修改为创建AggregateCacheDependency类型对象的方法。

然而这样的做法扰乱了作为工厂类的DependencyAccess的本身职责,且创建IPetShopCacheDependency接口对象的行为仍然有可能被调用者调用,所以保留原有的DependencyAccess类仍然是有必要的。

在PetShop 4.0的设计中,是通过引入Facade模式以方便调用者更加简单地获得AggregateCacheDependency类型对象。

4.3.4  引入Facade模式

利用Facade模式可以将一些复杂的逻辑进行包装,以方便调用者对这些复杂逻辑的调用。就好像提供一个统一的门面一般,将内部的子系统封装起来,统一为一个高层次的接口。一个典型的Facade模式示意图如下所示:

4-4.gif
图4-4 Facade模式

Facade模式的目的并非要引入一个新的功能,而是在现有功能的基础上提供一个更高层次的抽象,使得调用者可以直接调用,而不用关心内部的实现方 式。以CacheDependency工厂为例,我们需要为调用者提供获得AggregateCacheDependency对象的简便方法,因而创建了 DependencyFacade类:
public static class DependencyFacade
{
    private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
    public static AggregateCacheDependency GetCategoryDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateCategoryDependency().GetDependency();
        else
            return null;
    }
    public static AggregateCacheDependency GetProductDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateProductDependency().GetDependency();
        else
            return null;
        }
    public static AggregateCacheDependency GetItemDependency()
    {
        if (!string.IsNullOrEmpty(path))
            return DependencyAccess.CreateItemDependency().GetDependency();
        else
            return null;
    }
}

DependencyFacade类封装了获取AggregateCacheDependency类型对象的逻辑,如此一来,调用者可以调用相关方法获得创建相关依赖项的AggregateCacheDependency类型对象:
AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();

比起直接调用DependencyAccess类的GetDependency()方法而言,除了方法更简单之外,同时它还对CacheDependencyAssembly配置节进行了判断,如果其值为空,则返回null对象。

在PetShop.Web的App_Code文件夹下,静态类WebUtility的GetCategoryName()和GetProductName()方法调用了DependencyFacade类。例如GetCategoryName()方法:
public static string GetCategoryName(string categoryId)
{
     Category category = new Category();
     if (!enableCaching)
            return category.GetCategory(categoryId).Name;

     string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);

     // 检查缓存中是否存在该数据项;
     string data = (string)HttpRuntime.Cache[cacheKey];
     if (data == null)
     {
           // 通过web.config的配置获取duration值;
           int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
           // 如果缓存中不存在该数据项,则通过业务逻辑层访问数据库获取;
           data = category.GetCategory(categoryId).Name;
           // 通过Facade类创建AggregateCacheDependency对象;
           AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
           // 将数据项以及AggregateCacheDependency 对象存储到缓存中;
           HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
      }
      return data;
}

GetCategoryName()方法首先会检查缓存中是否已经存在CategoryName数据项,如果已经存在,就通过缓存直接获取数据;否 则将通过业务逻辑层调用数据访问层访问数据库获得CategoryName,在获得了CategoryName后,会将新获取的数据连同 DependencyFacade类创建的AggregateCacheDependency对象添加到缓存中。

WebUtility静态类被表示层的许多页面所调用,例如Product页面:
public partial class Products : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
    }
}

显示页面title的逻辑是放在Page_Load事件方法中,因而每次打开该页面都要执行获取CategoryName的方法。如果没有采用缓存机制,当Category数据较多时,页面的显示就会非常缓慢。

4.3.5  引入Proxy模式

业务逻辑层BLL中与Product、Category、Item有关的业务方法,其实现逻辑是调用数据访问层(DAL)对象访问数据库,以获取相 关数据。为了改善系统性能,我们就需要为这些实现方法增加缓存机制的逻辑。当我们操作增加了缓存机制的业务对象时,对于调用者而言,应与BLL业务对象的 调用保持一致。也即是说,我们需要引入一个新的对象去控制原来的BLL业务对象,这个新的对象就是Proxy模式中的代理对象。

以PetShop.BLL.Product业务对象为例,PetShop为其建立了代理对象ProductDataProxy,并在GetProductByCategory()等方法中,引入了缓存机制,例如:
public static class ProductDataProxy
{

    private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings["ProductCacheDuration"]);
    private static readonly bool enableCaching = bool.Parse(ConfigurationManager.AppSettings["EnableCaching"]);
       
    public static IList
GetProductsByCategory(string category)
    {
        Product product = new Product();

        if (!enableCaching)
            return product.GetProductsByCategory(category);

        string key = "product_by_category_" + category;
        IList data = (IList )HttpRuntime.Cache[key];

        // Check if the data exists in the data cache
        if (data == null)
        {
            data = product.GetProductsByCategory(category);

            // Create a AggregateCacheDependency object from the factory
            AggregateCacheDependency cd = DependencyFacade.GetProductDependency();

            // Store the output in the data cache, and Add the necessary AggregateCacheDependency object
            HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(productTimeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
        }
        return data;
    }
}

与业务逻辑层Product对象的GetProductsByCategory()方法相比,增加了缓存机制。当缓存内不存在相关数据项时,则直接 调用业务逻辑层Product的GetProductsByCategory()方法来获取数据,并将其与对应的 AggregateCacheDependency对象一起存储在缓存中。

引入Proxy模式,实现了在缓存级别上对业务对象的封装,增强了对业务对象的控制。由于暴露在对象外的方法是一致的,因而对于调用方而言,调用代理对象与真实对象并没有实质的区别。

从职责分离与分层设计的角度分析,我更希望这些Proxy对象是被定义在业务逻辑层中,而不像在PetShop的设计那样,被划分到表示层UI中。 此外,如果需要考虑程序的可扩展性与可替换性,我们还可以为真实对象与代理对象建立统一的接口或抽象类。然而,单以PetShop的表示层调用来看,采用 静态类与静态方法的方式,或许更为合理。我们需要谨记,“过度设计”是软件设计的警戒线。

如果需要对UI层采用缓存机制,将应用程序数据存放到缓存中,就可以调用这些代理对象。以ProductsControl用户控件为例,调用方式如下:
productsList.DataSource = ProductDataProxy.GetProductsByCategory(categoryKey);

productsList对象属于自定义的CustomList类型,这是一个派生自System.Web.UI.WebControls.DataList控件的类,它的DataSource属性可以接受IList集合对象。
不过在PetShop 4.0的设计中,对于类似于ProductsControl类型的控件而言,采用的缓存机制是页输出缓存。我们可以从ProductsControl.ascx页面的Source代码中发现端倪:
<%@ OutputCache Duration="100000" VaryByParam="page;categoryId" %>

与ASP.NET 1.x的页输出缓存不同的是,在ASP.NET 2.0中,为ASP.NET用户控件新引入了CachePolicy属性,该属性的类型为ControlCachePolicy类,它以编程方式实现了对 ASP.NET用户控件的输出缓存设置。我们可以通过设置ControlCachePolicy类的Dependency属性,来设置与该用户控件相关的 依赖项,例如在ProductsControl用户控件中,进行如下的设置:
protected void Page_Load(object sender, EventArgs e)
{
    this.CachePolicy.Dependency = DependencyFacade.GetProductDependency();
}

采用页输出缓存,并且利用ControlCachePolicy设置输出缓存,能够将业务数据与整个页面放入到缓存中。这种方式比起应用程序缓存而 言,在性能上有很大的提高。同时,它又通过引入的SqlCacheDependency特性有效地避免了“数据过期”的缺点,因而在PetShop 4.0中被广泛采用。相反,之前为Product、Category、Item业务对象建立的代理对象则被“投闲散置”,仅仅作为一种设计方法的展示而 “幸存”与整个系统的源代码中。

转载于:https://www.cnblogs.com/chenying99/archive/2011/03/14/1983125.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/380675.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用python学线性代数_最简单的神经网络简介| 使用Python的线性代数

使用python学线性代数A neural network is a powerful tool often utilized in Machine Learning because neural networks are fundamentally very mathematical. We will use our basics of Linear Algebra and NumPy to understand the foundation of Machine Learning usin…

电脑安装了mysql,但找不到mysql服务

首先找到mysql的bin文件目录&#xff0c;我的是在&#xff1a;C:\Program Files\mysql-5.7.27-winx64\bin 然后找到下图内容&#xff0c;右键以管理者身份运行 然后cd 到mysql的bin目录 在输入mysqld -install 服务就重启好了

win10安装masm32 SDK并运行一个小程序

建议在安装之前&#xff0c;先装一下notepad编辑器&#xff08;其他也行)&#xff0c;Visual C 首先我们到官网下载masm32&#xff08;http://www.masm32.com/&#xff09;&#xff0c;到了官网后&#xff0c;点击download就行了。 随便点一个就可以了。 将zip文件解压&#…

matlab figure 嵌套,操作Matlab的Figure窗口(一)

以前&#xff0c;我和很多人一样&#xff0c;总是将数据保存到mat文件中。后来突然发现&#xff0c;如果数据量不大的话&#xff0c;直接将Figure窗口中的图形保存为fig文件是更好的选择。fig文件与一般的图像文件不同&#xff0c;并不是由图像的像素构成&#xff0c;它包含了当…

c语言中fflush函数z_带有示例的C语言fflush()函数

c语言中fflush函数zC中的fflush()函数 (fflush() function in C) Prototype: 原型&#xff1a; int fflush(FILE *filename);Parameters: 参数&#xff1a; FILE *filenameReturn type: 0 or EOF 返回类型&#xff1a; 0或EOF Use of function: 使用功能&#xff1a; When …

masm32使用nmake工具

nmake.exe如果你安装了Visual C你可以在bin文件目录下找到&#xff0c;然后复制到masm32的bin目录下,如果没有安装visual C就百度下一个吧&#xff01; 使nmake之前&#xff0c;我们的.obj和.res文件都创建好了,在工程建一个文件Makefile&#xff0c;不需要后缀 里面内容填&am…

SQL Server 2005 中的商务智能和数据仓库

SQL Server 2005 中的商务智能和数据仓库 发布日期&#xff1a; 2005年03月11日摘要&#xff1a;本文概述了 SQL Server 2005 Beta 2 中“商务智能”平台的增强功能。本文并非实施指南&#xff0c;而是为读者提供了关于“商务智能”平台增强功能的信息。本页内容 简介SQL Serve…

MVVM实践教程

算算&#xff0c;从事Silverlight和WPF的开发也有1年多的时间了&#xff0c;虽然时间不算长&#xff0c;虽然还没有突出的成就&#xff0c;但是感觉也还算一般。 但是&#xff0c;从头至今都没有去认真研究和使用过MVVM&#xff0c;虽然它被认为是Silverlight和WPF开发的最佳架…

32位通用寄存器ESP、EIP、EAX、EBX、ECX、EDX,在OD里操作这些寄存器

EIP&#xff1a;指向CPU下一步即将执行的指令 I EIP为00401000&#xff0c;表示&#xff0c;CPU下一步到00401000这个地址去执行&#xff0c;下一步执行push 0x0指令 ESP&#xff1a;始终指向堆栈的最顶端 现在的ESP是0012FFC4&#xff0c;现在堆栈的顶部在这个地址 EAX、EBX、…

汇编add和mov指令

汇编指令add&#xff1a; 格式&#xff1a;add 参数1&#xff0c; 参数2 功能&#xff1a;参数1和参数2相加&#xff0c;将结果赋值给参数1&#xff0c;即 &#xff1a;参数1参数1参数2 汇编指令 mov&#xff1a; 格式&#xff1a;MOV destination,source 功能&#xff1a;将源…

双层玻璃窗的功效模型matlab,数学建模:双层玻璃窗的功效,80人%的人搞不懂数学的应用价值...

原标题&#xff1a; 数学建模&#xff1a;双层玻璃窗的功效&#xff0c;80人%的人搞不懂数学的应用价值A.Einstein有一句名言&#xff1a;想象力比知识更重要&#xff0c;因为知识是有限的&#xff0c;而想象力包括世界的一切&#xff0c;推动着进步&#xff0c;并且是知识的源…

8086CPU物理地址

8086CPU有20位地址总线&#xff0c;可以传送20位地址&#xff0c;达到1MB寻址能力&#xff0c;但8086CPU内部是16位&#xff0c;表现出来的寻址能力只有64kb。 8086CPU采用一种在内部用用两个16位地址合成的方法来形成一个20位的物理地址。 CPU中的相关部件提供两个16位地址&a…

8086的CS段寄存器(IP)

8086共有四个段寄存器&#xff0c;分别为CS&#xff0c;DS&#xff0c;SS&#xff0c;ES CS为代码段寄存器&#xff0c;还有个与CS息息相关的寄存器叫IP&#xff0c;为指令指针寄存器。 在8086PC机中&#xff0c;设CS中的内容为M&#xff0c;IP的内容为N&#xff0c;8086CPU将从…

思绪飞扬(一)

喜欢一个呆在房间了&#xff0c;听着音乐&#xff0c;冲一杯香甜的牛奶。任自已的思绪在空气中放飞&#xff01;似乎这一刻天地之间只有自已。 想想自已已经历了人生的近三分之一的时光。回首之间&#xff0c;二十几年的时间&#xff0c;我们经历了很多事&#xff0c;有…

nginx php fpm 日志,nginx下php-fpm不记录php报错日志怎么办?

解决nginx下php-fpm不记录php报错日志的办法&#xff1a;1、修改【php-fpm.conf】中配置&#xff0c;没有则增加&#xff1b;2、修改【php.ini】中配置&#xff0c;没有则增加&#xff1b;3、重启【php-fpm】即可。解决nginx下php-fpm不记录php报错日志的办法:1.修改php-fpm.co…

【转贴备忘】[教程]iPhone 實機開發 Part 1 - HelloWorld

http://bbs.weiphone.com/read-htm-tid-222380-page-1.html最近小弟開始在研究 iPhone 的開發,無奈找到的資料都是舊的,也不知道怎麼放到 iPhone 上,應該很多人都跟我有同樣的困擾吧!經過幾天的研究總算寫出第一個 HelloWorld,而且可以不用付99元美金就可以放到實機上執行! 今天…

DS和[address]

8086CPU中&#xff0c;用16位寄存器来存储一个字。高8位存放高位字节&#xff0c;低8位存放低位字节。在内存中存储时&#xff0c;由于内存单元是字节单元&#xff0c;则一个字要用两个地址连续的内存单元来存放&#xff0c;这个字的低位存放低地址单元中&#xff0c;高位字节存…

计算机指令格式_计算机科学组织| 指令格式

计算机指令格式指令格式 (Instruction format ) Instruction format describes the internal structures (layout design) of the bits of an instruction, in terms of its constituent parts. 指令格式按照指令的组成部分描述指令位的内部结构(布局设计)。 An Instruction fo…

ejs获取js变量值_EJS变量(注入值)

ejs获取js变量值Hi! Welcome to NODE AND EJS TEMPLATE ENGINE SERIES. Today, we will talk about EJS variables or how we can inject values? 嗨&#xff01; 欢迎使用NODE和EJS模板引擎系列。 今天&#xff0c;我们将讨论EJS变量或如何注入值&#xff1f; Just like nor…

Visual Basic 9.0 前沿播报·静态篇(六)松弛委托和可为空类型语法增强

本期介绍的两个VB9作为数据开发辅助功能的新特性。CLR在.NET 2.0增加了许多关于委托的新特性&#xff08;但大部分都不为任何语言所支持&#xff09;&#xff0c;其中一个叫做协变的特性允许放松委托与函数绑定时的签名检查&#xff08;C# 2.0支持&#xff09;。为了追求更高的…