规划与架构设计(译)

来源:互联网 发布:java分布式事务 编辑:程序博客网 时间:2024/06/03 02:40

 

规划与架构设计

设计

正如我们在“问题”部分所解释的,在我们着手进行网站系统开发的其余部分之前,有很多问题需要我们解决、我们还需要做出一些关键的决定,这些工作构成了所有未来开发工作的基础设施。一旦你已经进行到开发工作的一半,这时去设计一个支持多种数据源的架构就太晚了。同样的,当你已经完成了所有会使用数据库的模块的代码工作后,你将不能就如何处理配置设置---如数据库连接串作出战略层次上的决策。假如我忽略这些问题并首先开始着手进行主要模块的代码编写,你最终将创建一个很烂的的系统,此系统在以后很难进行任何改进。在本项目的开发过程中,我们会用到很多新的ASP.NET 2.0的类和控件,当这些类和控件出现在我们开发的不同部分的代码中后,在某些时候这些新的功能将对一个特定的问题提供一个完整的和现存的解决方案。

设计一个分层的基础架构

假如你在近几年从事过软件开发,你应该很熟悉多层次的软件设计。现在我们用简短的几句话来对其进行回顾,多层次的软件设计将一个软件项目划分为功能、组件和代码不同的层。一般来讲,有四个层次:

  • 数据存贮:这是存贮数据的地方。它可能是一个关系型数据库、一个XML文件、一个文本文件、或一些专有的存贮系统。

·         数据访问层:这是指那些负责对在数据存贮系统中的记录进行提取和处理的代码。

·         业务逻辑层:这是指那些负责从数据访问层得到需要的数据并将它们以一种更容易理解的方式提交给用户使用的代码,业务逻辑层隐藏了底层的细节,如数据存贮模型、并增加了一些确保数据输入一致和安全的校验逻辑。

·         表达层(用户界面):这是指那些决定用户在计算机屏幕上看到什么界面的代码、这些代码包括对数据进行格式化并提供系统导航菜单。在ASP.NET应用项目中,该层被设计为在一个浏览器中进行操作,但是在其它一些应用程序中也许会使用windows Form

根据项目大小的不同,你可能还有更多的层次,或者将几个层次合并为一个层次。例如,在一个小项目中,我们可能把数据访问层和业务逻辑层合并为一层,这样只有一个部件负责提取数据并将数据以一种更容易理解的方式提交给用户界面。

在对多层的架构和设计进行讨论的时候,我们经常笼统地使用层这个词,但是实际上这里存在一些微妙的不同之处:这里存在对部件进行物理分离的层,这些部件可能意味着在同一台计算机上或不同的计算机上的不同组件(DLLEXE或者其它的文件类型---如果不是.NET体系的话);另外还存在对部件的逻辑分离的层,这样的层拥有不同的类和用于DALBLLUL层代码的名字空间。因此,我们将tier用来表示物理区分和部署单元,而使用layer来表示逻辑区分和设计单元。

在本项目的前一个版本里,我们为网站的创建了很多单独的组件,每一个组件都由一个单独项目进行编译并将它们加入整个解决方案之中。当然。对于大型的企业级网站,那是一种好方法,但是对于中小型网站的开发,难免有杀鸡用牛刀的嫌疑,因为要处理很多不同的VS项目以及它们之间的关系和相互依存状态是很困难的。在很多中小型网站,一个很好的,逻辑上的多层设计架构,在很多情况下对你来说就足够了。而ASP.NET 2.0也提供了额外的诱因来使我们使用一种更加简单的部署模式:将所有的代码文件放在专门的App_Code文件夹中(以及它的子文件夹),而App_Code文件夹则在你的主要应用程序的文件夹之下,并在运行时会被自动地编译,并有应用程序中的其它部分所使用。这种自动和当需要的时候进行编译的机制,使你在调试你开发的页面时觉得更加容易和快速,因为你可以在网站运行的情况下对源代码文件进行某些修改(甚至加上debugger),而编译将在页面下一次被请求时进行。此过程叫着“编辑和继续运行”---edit and continue

现在,组件编译的老选项你仍然可以使用,但是新的选项对于ASP.NET 2.0的网站来说会使你的开发工作变得更加容易,并使得网站的部署更加容易。在本项目中,我们要开发的网站包括数据访问层、业务逻辑层和用户界面层代码,它们组合在一个VS项目中。如果你希望有更完美的层分离(例如,为了安全的原因,你向把数据访问层放在与web服务器分离的单独服务器上),你仍可以创建单独的VS项目,将数据访问层放入这些项目,然后编译它们形成单独的、可部署的组件,但是在这个项目中,我不会使用此方法。

虽然我们只使用一个VS项目,我们还是会使用不同的文件夹来对在文件系统中的,属于不同逻辑层的文件进行组织(App_Code/DALApp_Code/BLL、以及拥有子目录的根目录),并且我们会使用不同的名字空间在网站的对象模型中对类进行逻辑上的组织(MB.TheBeerHouse.DALMB.TheBeerHouse.BLL以及MB.TheBeerHouse.UL)。也就是说,我们将使用不同的分离方式,除了它们都是同一个项目中的一部分这个事实。

选择一个数据存贮系统

在本项目中,我们将开发一个具有灵活性的数据访问层,以支持不同的数据系统并能够在不需要对其它层做任何改变的情况下快速地在不同的数据系统之间切换。但是,在你已经开发了底层的数据存贮架构和针对你希望支持的不同数据源相应的数据访问层后,这只是对于在不同的数据源之间进行切换变得容易。因此,如果需要支持其它的数据库,仍需要花费可观的时间和精力来做需要的开发工作,但是我们使用的多层设计方式可以降低由此带来的痛苦。

对于我们将要开发的应用程序,哪种数据库系统是比较适合的呢?如果我们将要开发的网站其内容不会经常改变,保持相对的静态的话,我们可以只使用XML文件即可,或者我们可以使用微软的Access数据库。在很多方面,Access数据库都比XML文件好用,但是,它仅仅是一个桌面数据库系统,它并不适合我们需要应对的情况---管理大量的数据,即使我们要开发的网站只需要支持少数并发用户访问。Access没有伸缩性,所以对于网站来说,我们强烈的建议不要使用它。这样,我们就需要考虑目前市场上流行的关系型数据库系统,如SQL ServerOracleDB2MySQLPostgress等等。对于我们的需求来讲,所以上面说到的数据库系统都很好,但是对于此项目的目的来讲,我们只需要选择一种数据库系统。

在你的客户市场,你的客户可能希望你使用某种特定的数据库系统,或者当网站系统部署后,他们希望更换数据库系统。对于用户来说,他们希望使用特定的数据库系统总是有一些原因的,因为他们对某特定的系统有相应的知识和经验,这样在你将网站卖给他们后他们自己可以进行相关的维护;或者他们已经购买了数据库系统,所以想充分的利用,而不是另外再去买另一个供应商的软件;或者他们仅仅是喜欢每一种特定的数据库系统。在这种情况下,你需要向用户解释为什么要选用某种特定数据库的原因,如果你告诉他们这对于他们是最好的选择(这应该是顾问所做的事情),而客户可能会因为自尊坚持他们的决定。

大多数.NET软件开发者都选择微软的SQL Server,有许多理由这样作这样的选择。因为SQL同使用的开发工具VS有很好的集成、从一个供应商处购买开发软件和数据库系统软件很简单,但我个人喜欢说的原因是SQL的价格相对较低,而且在Windows平台上具有高性能,另外非常容易管理。在决定了使用SQL Server后,现在我们需要决定使用SQL Server的什么版本。在本项目开发中,我们将使用Visual Studio 2005,所以使用最新版本的SQL Server来配合是很合理的,其名字是SQL Server 2005。微软公司也发布了SQL Server 2005的免费版本,可以在开发人员的计算机上以及在网站的服务器上使用,它就是SQL Server 2005 Express Edition。这是以前免费的,老版本的微软数据库引擎(MSDE)的新版本,当然免费的版本存在一些限制,另外它有一个新的图形界面的管理工具以及很多新的功能。

SQL Server 2005 Express EditionSQL Server 2005的商用版本都可以用于我们的网站。对于我们将开发的网站来说,它们的功能都是一样的,主要的不同就是图形化的管理工具,而不是下面的关系型数据库引擎。所有版本的SQL Server 2005都与VS2005集成得非常好,在VS中的Server Explorer工具能使开发人员浏览注册的服务器和数据库、视图、类型以及相互关系。事实上,我们可以在VS2005中完成所有网站数据库的开发工作,而不需要使用SQL Server的管理工具!在VS 2005中新的Diagramming工具甚至使我们可以以可视化的方式来设置表之间的关系、设置约束条件以及foreign key的关系。

一些SQL Server 2005的新功能包括同.NET运行时紧密的集成(SQL作为CLR的宿主),这使我们可以使用C#VB.NET来编写UDFs(用户定义的功能)UDTs(用户定义的类型);另外,SQL Server 2005引进XML格式作为一种支持的数据类型(这意味着我们可以为一个字段选择XML作为其数据类型,然后针对此字段以及此字段的索引,我们可以使用特殊的功能和操作来执行很快的查询和过滤。);新的特性(如ROW_NUMBER, 这使我们可以很容易地实现满足需求的分页);以及Service Broker技术,这使我们可以创建基于消息的异步数据库应用程序;更多更好的安全许可,等等。Express版本的限制是只支持一个CPU1G的内存,最大的数据库大小为4G。同时也没有高级的功能,如数据库分区、数据库镜像、通知服务以及全文搜索。但是,对于大多数中小型的网站或Web应用程序,SQL Server Express是一个可以接受的选择。对于用户来说,可以在开始的时候使用它,当需要在高端的服务器上拥有更好的性能时,用户仍然可以在不进行任何代码修改的情况下对数据库进行升级。

对于Express版本。我想强调的最后的好处是可以实现使用xcopy的网站部署方式:你只需要将数据库文件放在本地的子目录内(对于网站有一个专门的目录App_Data),然后使用新的属性AttachDBFilename,在连接串中指定文件路径来动态地将数据库文件挂入系统。这样,就可以使用xcopy将所有的目录结构拷贝到远程服务器上,然后网站就可以正常运行了,而不需要对数据库服务器做任何的配置工作。Dec-17, 2008

数据访问层设计

数据访问层是对数据库系统执行查询的代码,用于提取数据、对数据进行更新、插入数据、以及删除数据。它是与数据库系统联系最紧密的代码,因此它必须知道所有数据库的细节,例如,数据库表的模型、字段的名字、存贮过程、视图等等。在本项目的设计开发中,我们将把用于处理数据库的代码与应用程序页面分离开,理由如下:

·         开发用户界面的工程师(页面和用户控件)与编写数据库访问代码的工程师可能不是由同一个人负责。事实上,对于大中型的web应用程序,这些工作通常都有不同的人负责。负责用户界面的开发人员可以不理会有关数据库的大多数事情,但仍需要为数据库有关的事情提供用户界面,因为所有的细节都封装在单独的对象里,这些对象则提供对表、存贮过程以及字段名,处理它们的相关SQL语句高度抽象。

·         有一些负责提取数据的查询会典型的在多个页面被使用。如果我们将它们直接放入页面,而在以后需要对查询进行更改以增加字段或改变排序规则,那么就必须检查所有的代码并找出所有需要进行改动的地方。而如果我们将有关数据访问的代码放在通用的数据访问层,我们就只需要对数据访问层的代码进行修改,而调用它们的页面则保持不变。

·         直接在页面中写的,不灵活的查询代码在需要改变使用的数据库系统或支持多种不同的数据库系统时会给我们带来很大的麻烦。

使用接口模式的设计方式来提供对多个数据系统的支持

在我们的设计中,一个最重要的考虑是,我们提供的系统可能需要对不同的数据库系统或数据源进行支持。我们将要开发的系统应该是通用的,能很容易地被不同的用户采纳。但是,正如我以前说的,不同的客户对于使用什么样的数据库系统有不同要求,这就会使我们不得不支持使用OracleMySQL,而不是SQL Server 2005来作为应用系统的数据库系统。如果我们在事先没有为这样的可能性做必要的准备,我们就可能在以后遇到这种情况时,因为重新修改代码而陷入困境。不同的数据库系统有不同的功能和使用不同的SQL语言、存贮过程、使用不同的SQL语句参数、使用不同的语法来传递参数、数据类型也是不同的,等等。对于一个在现实世界里中等规模和中等复杂性的应用系统,拥有一个通用的数据访问层,并以相同方式来处理所有市场上的数据库系统是不可能的。

如果试图使用OleDb接口来写一个通用的基类代码,你会很快发现在代码中会无穷无尽地充斥着“如果我使用此数据库做这个,或者假如我使用另外一种数据库做那个-----if…else if…这样的代码块。即使你能容忍你的代码乱成一锅粥,在某些情况下,这样的方法也行不通。例如,如果我们的系统需要让其他软件经销商去销售,他们可能会将此软件系统和一个更大的解决方案整合,而我们并不知道他们会怎样使用数据库,因为我们希望提供的是一个可能提交给成千个用户的商业软件。在这种情况下,我们不可能为所有的数据库提供支持。我们也不可能向他们提供源代码,这样他们可以进行修改以支持他们使用的数据库,因为我们需要保护我们的软件投资。这也是很多新的ASP.NET 2.0模块所会遇到的问题,这包括成员管理、用户档案管理、个性化功能、会话存贮机制、等等。微软公司为SQL Server数据库系统提供了一个内建的数据访问层,同时也支持某些数据库系统,但并不是所有可能的数据库系统;而我们希望能够通过使用接口模型的设计方法来提供这样的支持。

我们需要做的不是直接去编写数据访问层的类,而是先编写一个基本的抽象类,此类定义一个公共接口(用于数据访问的CRUD方法的签名),在需要的情况下使用一些helper方法。而真正访问数据的代码则放在一个从基类继承的另外一个类中,并为其抽象的方法提供具体的实施办法,我们称这样的类为接口,它们是用于某种特定的数据库系统的;这样,在我们开发针对某特定数据库的接口时,我们不必考虑同其它数据库的兼容问题,因为我们可以在开发一个针对另外一种数据库的接口。

然为了更好地对不同的接口进行组织,我们将针对不同数据库或数据源的接口放在不同的目录和名字空间下面(例如,App_Code/DAL/SqlClient)。在基类中,我们设计一个专门得到对象实例的静态属性(或者使用一个GetInstance的方法),此属性的作用就是创建和返回根据配置文件中指定的接口类的实例对象。此属性或方法为业务逻辑层的类所使用以得到具体数据访问层对象的引用,而具体的数据访问层对象负责数据提取和修改的工作。上面说到的业务逻辑类则会由用户界面层所使用,这样用户界面层与下面的数据访问层没有直接的关系。这种方式可以使我们的软件工程师编写超高速的代码,因为可以利用特定数据库系统的所有最优化的选项。而代码也会显得非常清晰,因为你不必写诸如If…else这样的代码块来应付不同的数据库系统。这种方式也允许我们对整个系统进行编译并向用户提交编译后的组件:如果他们想使用我们的系统不支持的数据库,他们可以使用从我们基本数据访问层的类继承的方式来开发自己的接口,然后在配置文件中做一个指向即可。

你也许会发现并指出,如果使用这样的方法的话,实际上我们是为每一个数据库系统或数据源都编写了一套数据访问层代码。差不多是这样,因为这里的问题变成了是否针对一个数据访问层进行重复的实现工作还是对系统中的所有模块进行重复的实现工作。另外,也应该考虑到我们也许只想对于那些根据不同的数据库而不同的方法应用这种设计方式。越简单的方法,越能在不同的数据库或数据源上很好的工作而不需要任何修改,这些方法可以使用OleDb接口和数据访问层基类中的通用代码,这样,让所有的接口都从它继承而来,从而就不需要重新实施这些简单的方法。在需要的时候,这些方法可以设置为virtual从而让新的接口替代相应的功能。下面以图形化的方式表示了用户界面层、业务逻辑层、数据访问层以及数据源之间的关系。


UI/BLL/DAL/Data Store
之间的关系

在以后的开发过程中,我们还将讨论这种设计方法。在以后,我们将在新的认证、成员管理和用户档案模块的开发中使用此设计方法。然后,我们在对数据访问层的设计和开发中使用这种方法,以支持文章管理模块。Dec-18, 2008

永恒的问题:使用数据集还是定制的业务实体?

当在业务逻辑层的类调用数据访问层的一些方法来提取数据的时候,你认为它们应该如何接收这些提取出来的数据?使用一个DataSet/DataTables,或者使用一个定制业务实体类对象的集合,用它包装从数据库提取出来的字段数据?如果设立一个在.NET架构师和开发人员中最长时间和最热门的争论话题奖,此话题无疑会最终获奖。你只要在google搜索”DataSet vs. custom entities””DataSet vs. custom collections”,或者”DataSet vs. domain objects”,你会找到大量关于此话题的看法!两种方法都有其优势和劣势,在不同架构中它们都有其自己的位置。

如果我们选择在业务逻辑层和数据访问层之间传递数据使用DateSets/DataTables的话,需要使用ADO.NET的方法去访问传递到业务逻辑层中的数据。但是如果我们使用指定的业务实体的话,所有的数据都将包装在定制的类中以及类的集合中,这样我们就可以用更符合工作习惯的方法去访问在业务逻辑层中的数据,而这些方法是专门为有关问题的特定数据所定制的。

大多数人都同意,对于基于桌面的聪明客户端应用程序来说,DataSet/DataTablesx显然是最佳的选择,但对于具有伸缩性和高性能的web应用程序来说就不是那样了。在我提到DataSet的时候,我实际上的意思是指typed DataSet,因为untyped DataSet有很多缺点:容易键入错误的表信息、错误的字段名或关系名、或在设置它时键入错误的字段类型,因此可能需要大量的时间来找到和纠正暗藏的代码错误。而typed DataSet就很容易使用,因为会得到智能指示在字段名上的指引,也提供内建的排序和过滤,不管是对Windows应用程序或ASP.NET应用程序,它都全面地支持数据绑定,并且它们同VS集成开发环境也紧密集成。DataSetDataTables都是可以序列化的(Serializable)(在.NET 1.x中,DataTables不支持序列化,必须将之加到DataSet中以序列化它们),并且.NET 2.0现在支持真正的二进制序列化,而不像在.NET 1.x中总是以XML格式进行序列化,即使你使用了二进制格式(它只给你一个二进制的头,但真正的序列化内容是XML的)。最后,DataSet可以很容易地支持处理并发状况的不同策略,因为DataSet的行会同时保存来自数据源的原始数据,还会保存当前的数据值(这是用户修改后的数据)。

DataSet/DataTables的缺点表现在三个方面:有性能和伸缩性限制、数据表达、以及业务规则的确认。如果只是传递一行数据,你仍然需要创建和传递整个DataSet/DataTables(这非常浪费资源);并且因为它是一个处于内存中的迷你数据库,肯定会使用相当的资源。它与数据库的代表物---关系型的表模式紧密联系,而不是一个清晰而可定制的,面向对象的数据代表物。虽然它与VS集成紧密,只要数据库模型改变一点(重新命名一个字段、添加或删除),就需要对typed DataSet进行重建,这比修改一个定制的集合类要困难。但是,.NET开发人员最感到痛苦的是,不能向一个DataSet容易地添加定制的业务和确认逻辑,所以,开发人员不得不写很多丑陋的代码,以在当前的和新的数据值被存放到数据库之前,或在其它一些语句运行之前为它们保持相应的业务逻辑。我看到过一些类库,它们能让你添加一些信息到DataSetEextendedProperties集合,或者到DataSetXSD模型,它们允许你将一些简单的确认规则同表和字段相关联:如确保一个字符串至少有10个字符长度或者必须满足给定的形式、一个整数或数字必须处于给定的范围中、以及类似的其它规则。这些都非常有用,但是,在现实世界中,需要做比输入确认更多的事情。你必须写代码以进行约束条件检查和确认,而这些动作必须执行以确保一个数字或一条记录是合法的,在其环境中是有意义的---例如,你不能在没有存货的情况下批准接受一份订单、如果信用卡没有通过第三方支付网关的确认,或客户数据库中的某个用户已经被标识为有风险,你不能使那个交易生效。这些domain validations需要定制的业务逻辑,这是不能靠使用一个通用的framework或类库就可以自动得到的。

虽然我举例说明在从业务逻辑层传递数据给用户界面层不好,并不意味着DataSet在我们的系统设计中一点用户也没有。实际上,我们会在数据访问层和业务逻辑层之间传递数据时偶尔会用到DataSet,但是在业务逻辑层和用户界面层之间传递数据时不会使用。在数据访问层,我们会使用ADO.NET的类,因为我们在这里与数据库打交道,因此在某些时候,在数据访问层使用DataSet是合理的。一旦我们给DataSet/DataTables装满了数据,将它往上传递给业务逻辑层应该不会花其它的资源。添加所有的确认逻辑则是业务逻辑层需要做的事情,并且业务逻辑层在进行确认的时候需要将通用的DataSet/DataTables转化成业务对象的定制集合;而这些定制的集合则会传递给用户界面层,在用户界面层,这些定制的集合代表一组功能强大的、专注于数据的面向对象程序设计类,对于用户界面的开发人员来说,它们是可进行数据绑定的,而且很简单。

.NET中,使用定制的业务实体对象的集合并不是一种新方法;这种方法已经使用了很多年了,甚至在.NET被开发出来之前很多年就开始使用了。一个定制的实体对象---A custom entity object其实就是一个以面向对象的方式包装从数据库中提取出来的数据的类,所以它对数据存贮模型和其它的细节进行了抽象。如果数据访问层和业务逻辑层是两个相互分离的层(这在大中型系统中很常见),会在两个层次上使用这些定制的集合:数据访问层和业务逻辑层之间;业务逻辑层和用户界面层之间。在第一个层次之间,实体类会非常简单,因为它只是将从数据库提取的数据包装起来,与数据库表有着一对一的关系(或者由多个表形成的数据视图),不需要在其中使用用于插入、更新、删除和提取数据的方法。在这样的使用场景中,它的作用就是一个容器,以在两个层次之间传递数据。而在第二个层次之间的实体类则会比较复杂:它们要包装数据,要具有用于引用父或子对象的属性,以及用于处理数据的方法实例。这样一些类经常被称之为domain objects,并且它们并不仅仅是实体类,它们的实例还应该完整的表达domain的元素(一份订单、一个员工、一个产品、一个新闻,等等)。创建实体类和domain objects肯定会更加复杂,需要更多的工作,而不仅仅是使用typed DataSet由设计器自动地创建,由产品本身的功能就能正确的进行排序和过滤,但是它们能更好地对数据库模型进行抽象、更简洁、更容易使用、而且能更直观地由用户界面层的开发人员所使用。Domain objects对于开发人员来说更容易进行维护,并且能快速地从数据库装载数据、使用的内存也更少,因为我们可以通过使用诸如lazy-load方式来实现只装载需要的记录,lazy-load只是在用户实际需要的时候才装载数据,而不是通过一个查询在同一时间装载所有的数据。同时,我们借此能实施一个定制的序列化机制,使我们想保存的数据以我们真正想用的模型保存起来。另外,向domain objects添加定制的确认逻辑也非常灵活,因为数据是以属性的形式进行包装,这样我们可以使用我们需要的定制逻辑数量对其进行扩展。

由此,我的结论如下,尽管我也在企业级应用程序中看到过使用DataSet,定制的对象显然会给我们提供更多的灵活性,并使我们的系统更简洁,在我们要开发的系统中,我更愿意选择使用定制对象而不是DataSet,特别是在业务逻辑层和用户界面层之间传递数据的时候。当然,使用定制的对象需要我们付出更多的精力和时间以设计和开发业务逻辑层来得到上述优势。我将在这里简单的描述我们是如何做这些事情的,以及在这个过程中我们采用的最佳实践。在本系统的设计中,我们使用了完全分离的层次设计,并且使用了定制实体类和集合在数据访问层和业务逻辑层之间传递数据,我们使用了domain objects从业务逻辑层得到数据,并提供对数据提取和修改的支持,以在用户界面层更加容易的使用它们。

注意:一些公司开发出一种叫着ORM(Object Relational Mappers)的工具,这些工具可以很容易在数据库表、视图以及其它关系对象和一个OOP类之间进行映射。它们会自动地对数据库的模型进行研究,然后为你自动地创建用户包装的定制实体和domain object类。这些工具中的某一些运行在设计时间,自动地为你创建C#的类,然后你可以将之编译在你的应用程序中;而另一些在工作在运行时间,为你提供相应的映射功能。但是,有时这些工具并不能如你期望的那样为你提供需要的结果,而且你要使用它们还需要花相当的时间去学习如何使用,以适合你特定的数据库和使用需求。对于.NET开发人员来说最流行的这种工具是NHibernateEntityBroker、以及LLBLGen Pro。对于小型系统来说,一个可用的简单工具是Paul WilsonORMapper。在本系统的开发中,我们不会使用这些工具,我个人喜欢针对特定的系统开发定制的映射方案,但是对于其它机构来讲这可能是在某种情况下的最好选择。如果一个机构必须在规定的时间内为用户开发出一个新的系统,而那个系统会在以后由用户自己的人进行维护,最好让他们购买任何可能用到的第三方工具,因为这些工具的供应商一般都禁止没有授权使用。Dec 19-2008

使用存贮过程还是SQL查询?

对于数据库访问,有两种不同设计模式。一种是将所有的SQL语句都放在存贮过程中,然后只使用SqlCommand对象来根据名字执行这些存贮过程即可。另一种模式是将SQL语句以文本的形式在SqlCommand对象里执行。哪一种方法是最好的也已经辩论了很多年。在前面的阶段,我们已经讨论了有关在DataSet和业务实体对象之间进行选择的问题,那是最收到广泛争论的问题。而第二个受到最广泛争论的问题就是“在我设计的数据访问层中,我应该使用存贮过程还是SQL文本查询?”。就象对于第一个问题,不同的人会给你不同的答案和意见,而所有的答案都有其正面和反面。

我阅读了很多有关的书籍和资料,发现大多数都说存贮过程与SQL文本查询相比能提供更好的性能,因为存贮过程是“预编译了的”(我的意思不是编译成二进制文件,但是已经做了解析并使用结果生成了一个执行计划),并由SQL Server缓存在内存中了。但是,我发现很多书和资料都没有提到当你使用参数化的SQL语句时(你肯定可以在任何情况下这样做,来防止SQL语句注射这样的安全攻击),SQL文本查询同样做到了这样的事实(很多年以前,作为SQL Server的一个增强被加入SQL Server)。因此,在很多情况下,存贮过程和SQL文本查询的性能是相似的。当然,同SQL文本查询相比,存贮过程名字可以很简短并保持不变,因此对于SQL Server的引擎来讲可以更容易在缓存中找到执行计划,但是在很多情况下,这不会导致显著的性能差异。

存贮过程更提供更好的数据安全访问控制,因为我们可以赋予web用户账户相应的权限,这样他们只能执行特定的存贮过程,而不会给予他们对下面的表完全的控制权。如果不使用存贮过程,我们可以选择给用户在一个表上的INSERT许可,但是不给予他们在其它表上这样的许可,我们可以通过创建一个视图来限制用户访问其它的表字段(通过在视图中不包括这些表字段)。再一次,存贮过程在这方面能提供更好的控制,因为存贮过程可以使我们添加行一级的安全性:如果用户账户没有访问下面的表的权限,而只有访问一个根据特定的过滤器提取行信息的存贮过程的权限,这样的话,这个用户就不可能对其它的行进行信息的提取和处理。但是,对行安全性的需要确实很少。

另外一些喜欢使用存贮过程的理由是存贮过程可以包括一组语句,而不是一条语句。如果我们必须使用SQL文本查询的方式来执行这些语句,我们就不得不执行多个命令,这会因为发出和接收数据导致更多的网络负载。而当我们调用一个存贮过程的时候,网络的负载会很小,因为存贮过程的名字很短,不是大量的SQL语句,而SQL语句可能会达到数百个字符那么长。

存贮过程的另外一个优势是它会提供额外的代码层。如果我们在我们开发的系统中使用存贮过程,我们开发的数据访问层代码可以只与数据库有关,它会创建一个命令以引用一个存贮过程,设置其参数,然后执行存贮过程。这样,在我们的数据访问层就不会有任何SQL代码。这对于我们来说是非常有吸引力的,因为这样我们就可以对数据访问层进行编译,而在以后需要进行修改的时候只需要对存贮过程进行修改而不影响到数据访问层,就不需重新编译和分发数据访问层。但是,我并不是一个DBA,我对所有的规则、技巧以及最优化的查询代码的最佳实践并不熟悉,所以,在某些时候,在实施之前,我会让一个DBA帮我检查SQL代码。如果我使用存贮过程的话,让DBA帮我检查代码就会很方便,她可以帮我进行必要的修改,而这不会影响到我的数据访问层代码。她不需要了解C#(很多DBA都不是程序开发人员),她仅仅需要专注她擅长的T-SQL代码,而不需要对数据访问层的C#代码进行修改,这是一种很好的协作模式。

但是,存贮过程并不总是最好的解决方案。使用SQL文本查询的最大好处是这种方式最灵活。有些时候,我们希望在用户界面提供高级查询和过滤表单,这样,用户可以在文本框中输入部分数据以对一些数据库字段进行过滤并使用其它的字段对得到的结果进行排序。经常的情况是,我们希望得到的查询结果是可以分页显示的,因为如果结果包含的记录太多,我们不希望因此而让用户长时间等待大量数据的提交。查询根据用户填写的字段和排序选项支持所有这些不同的特性。如果我们在存贮过程中建立查询,那我们就必须写很多IF…ELSE语句,如果我们必须处理很多可选择的字段的话,我们的代码就会由此变得很难阅读和管理。通过使用动态的查询,基于用户在界面层的选择,在运行时间生成SQL文本,然后在一个SqlCommand中执行,所写的代码相比我们在存贮过程中为实现同样的功能所写的代码,会更简洁,并且维护性更好。

另一个使用存贮过程而不是SQL文本的考虑是,存贮过程使我们的代码同SQL ServerRDBMS更紧密的连接在一起,这样,如果我们需要支持另外的数据库的话,我们需要重新写大多数的存贮过程代码。因为不同RDBMS使用的存贮过程语言有很大的不同,即使它们的SQL语句句法与ANSI SQL规范很接近。但是,这样的考虑只会对想只开发一个数据访问层和使用标准的SQL语言,并提供对所有数据源移植支持的开发工程师有影响。在前面的部分我已经提到过,我不喜欢使用这样的方法:因为最后我的代码会变得非常复杂,充斥着大量的IF…ELSE代码块以处理所有数据类型的不同和不同的SQL构成;即使做了大量的努力,你也不能获得可能的最佳性能,因为可移植的代码不能利用高级的RDBMS的特定功能或特点,因为这些特点和功能是不可移植的。为了提供可移植性,我不会使用那种一个袋子装所有东西那样的方法,我宁愿使用接口的设计模式并为不同的RDBMS开发不同的数据访问层,这样应用程序才可能运行得更快并便于维护。如果我们需要支持多种数据库,接口模式当然不是必然的最容易的选项,但是在任何情况下,它所带来的好处都超过它所带来的缺点。

进行了上面所有的思考后,我得出的结论就是,在提取和处理数据库数据的情况下,我会使用存贮过程;在需要动态的查询并且写存贮过程会非常复杂的情况下,我会使用动态建立SQL文本这样的方式(使用SqlCommandSqlParameters)。Oct 27, 2008/Dec 20, 2008

开发一个由所有数据访问类使用的基类

如果你不熟悉抽象的基类的概念,这里是一个简单的解释,它是一个OOP类,此类具有其它的类所需要的基本功能。因为它是抽象的,所以不必为其中的每一个方法都提供具体的实现代码,但是可能需要为其中某些方法提供具体的实现代码---在我们将之设置为virtual的情况下,它们就可以由其它的子类进行覆盖(子类是继承此基类的类)。使用一个抽象基类可以做的事情是使用C#interface特性来指定需要接口类提供实现代码的通用签名,这个层次上此方法在不需要我们提供任何实现方法的代码。如果我们希望我们的数据访问层类针对接口设计模式进行开发的话,C#interface特性是最佳的选择,通过只使用interface提供的属性和方法,可以允许在运行时间执行不同的数据访问层实施代码(你肯定听说过即插即用,这就是即插即用功能)。

在我们的系统开发中,我们将使用抽象类,这会给予我们在继承树垂直方向上的重用能力。同时,通过使用interface,我们可以在整个树中具有侧面的可替代功能(实现多态性)。如果你还不熟悉这样一些OOP的概念和术语,我建议你去找一本有关C#的好书,因为你需要掌握这些重要的概念以为特定的应用系统设计最佳的架构。

在我们的系统中,每个模块都将有一个自己专有的,抽象的,位于数据访问层的基类,然后针对特定RDBMS开发一个或多个接口类以提供具体的实现代码。而所有的抽象类接口都将继承基类。此基类,我们简单的命名它为DataAccess,它有用一些属性以包装从web.config文件的定制区域读取的设置信息,以及对DbCommandSqlCommand, OleDbCommand, OracleCommand等等)对象的基本方法的包装:ExecuteNonQuery, ExecuteReader, ExecuteScalar。我们还可以使用helper方法来简化对command对象的参数和construction的创建(在前面提供的企业库会做到这些事情),但是我发现以标准的方式做的话已经足够简单,并且我相信在方法中写使用command的代码会使代码更容易阅读。下图说明了在DataAccess基类、抽象接口类以及它的具体实现代码之间的继承关系,以及这些包装后的command是如何工作的。



第一个抽象类---DataAccess,是所有数据访问层类的基类,其中使用了helper方法,以在以后应用于不同的数据访问层实施(通过继承的方式传递给下一代)。Dec 2020008主要的数据访问层的interface是在继承自MyDalObj的抽象类中进行定义,该定义定义了所有特定的数据访问层实施所需要的功能(属性和方法)。我在前面已经说过,在本系统的设计和开发中,我们只为SQL Server开发一个数据访问层的实施,因此我们专用于SQL Server的数据访问层实施就是上面图中所示的MySqlDalObj

DataAccess基类中的ExecuteNonQuery功能是一个特殊的helper方法,可以用它来传递存贮过程的名字、或在一个SQL文本查询中需要的表名,并将传递的名字以适当的,系统数据库所使用的名字规范(但是在我们系统的实际开发中,为了保持简单,我们不会去实现这个)。为了解释使用helper方法的原因,我想谈谈我以前得到的关于需要这个helper方法的经验。我开发了一个基于Web的内容管理系统或CMS(用于记录管理,它支持www.bestworks.ws和technet.bestworks.ws网站系统),在这个CMS的开发中,我在数据访问层的C#代码中使用明确的方式---hard code去引用数据库的表名和存贮过程的名字。然后,有一天,我需要对我开发的架构进行扩展以支持在同一个主机服务商机房处使用相同SQL Server数据库的其它类似网站(每个网站有不同的主机账户和数据库会使我的工作轻松很多,但是这种方式每个月的费用也很高)。这些需要支持的新网站的业务逻辑层和数据访问层应该与我开发CMS所支持网站一样,它们的不同点只是在用户界面层,当然,存放在表中的内容也是不同的。我的基本思路是对已有的网站进行复制,然后我就可以放心地根据需要对复制的网站进行修改。我遇到的问题是数据库架构没有设计为支持多个网站: 没有SiteID字段来让我将属于不同网站的内容分别存储起来,而且我发现在不大动的前提下,很难利用已有的数据访问层代码。

我思考是不是给每张表增加一个SiteID字段以让已有的表能保存不同网站的数据,或只是开发一个新的名字规范,此规范为每个不同的网站使用不同的表。最后,我认为不同的表会带给我更清晰的解决方案,而且不同网站的数据也可以分别进行备份,而且也支持让不同网站在以后使用不同的数据库和进行迁移的能力。我决定在所有表和存贮过程的名字前面使用前缀来确定属于那个网站。为了在数据访问层以简单的方式完成这样的修改,我决定在SQL代码和存贮过程的名字中使用可以替换的参数,这样我就可以在一个地方对它们进行修改以适应某特定网站。因此,我没有使用ups_DoSomethingTable1这样命名规范给存贮过程和表命名,我使用ups_Site1_DoSomethingups_Site2_DoSomthing给存贮过程命名,Site1_Table1Site2_Table1给表命名(ups用来定义使用表的CMS前缀,因此所有的网站都使用这个前缀)。为了简化对于数据访问层的代码修改,我增加了一个可替换的参数占位符,通过使用一个helper函数来用网站名替换占位符。但是,这就意味着我必须对整个数据访问策的代码进行逐行查找(几千行的代码),找到所有出现表名和存贮过程的地方,然后将占位符前缀加到它们前面(如usp_{InstanceName}_DoSomething),在修改代码以执行SQL文本和存贮过程以让它们去调用helper方法,从而用在配置设置中特定网站的值替换所有的占位符。我希望在开始的时候就使用在基类中的helper方法着手做这些事情,如ExcuteNonquery, ExecuteReader, ExecuteScalar,因此在本系统设计和开发的开始,我就使用此方法来进行架构设计。

为了容易理解和使用,我决定在本系统设计和开发不全面实施可替换的表和存贮过程名字系统。如果以后遇到的用户希望我们提供在一个服务器和数据库支持的环境下支撑多个类似的网站,那我们就需要在设计开始的时候就设计数据库来支持这种架构。但是,这次我们将开发的系统不会有这样的需求,因此我们不必使系统设计变得更加复杂。

另一个表明为什么基于基类的架构设计是一个好方法的例子是,当我测试本系统的早期版本时,我突然想到如果将此系统放在互联网上,这样用户就可以体验到它是如何工作的。这也许可以帮助用户决定是否购买该系统。本系统的很大一部分都是管理控制界面,让用户动态地添加、删除、修改和编辑系统的内容:文章、图片、论坛话题、新闻信、用户调查等等。因此,用户能实际的体验它们是很重要的,而不仅仅是看看用户界面。同时,我也不希望参观的用户能够修改我准备的内容例子。在我面临的环境中,管理用户界面是参观用户可以浏览的,但是他们按下上面的按钮则应该不会产生实际的作用,而我也不想为此例子网站开发一套新的用户界面来实现此需求。因为我有DataAccess这个基类,所有的方法都继承并使用它,因此我只需要在ExecuteNonQuery方法中添加一些代码就可以了(它执行所有的插入、更新和删除语句),这些代码指出当用户的身份是“SampleEditor”时,所有的命令都不会被执行。这是花了我一分钟的时间,而我得到了需要的结果。在系统的成员管理系统中,我们也使用了此方法来处理用户账户和成员的注册。

业务逻辑层设计

数据访问层由很多类构成,这些类通过执行存贮过程从数据库提取数据,然后以定制的业务实体类集合返回数据,其包装了被提取数据之字段。虽然包装在类中,数据访问层提供的数据仍是数据记录形式的,因为实体类没有增加任何其它东西;它们只是用来转移数据的一种强类型的容器。业务逻辑层将使用这些数据并将之提供给用户界面层,而业务逻辑层则会提供确认逻辑和处理之后的属性,设置某些属性为私有的或是只读的(在DALBLL之间使用的类中,它们仍是公共的和可写的) ,提供相应的数据处理方法---添加实例和静态方法以删除、编辑、插入和提取数据。对于代表一个员工的,名字为Employeedomain object,它可能有一个叫着Boss的属性,该属性返回代表另一个雇员类型对象的引用,其指出是第一个对象的老板。在一个中大型的项目中,经常存在上千个这样的对象,并需要说明它们之间的关系。这种针对任何数据的,面向对象和强类型的代表物为数据库提供了超级强大的抽象方式,它们目的仅仅是存贮数据,并为用户界面层的开发人员提供容易理解的、简单而强大的一组类,因此用户界面开发人员不需要知道有关数据记录是如何以及保存在什么地方、数据库中有多少表、表之间的关系是如何的这样的细节。这使得用户界面开发人员的工作变得更加容易,这也使我们可以对底层的数据架构进行修改而不会对任何用户界面层的代码产生影响(这就是使用多层架构设计的原因)。这种架构同只使用DataSet相比,在开始肯定需要更多的开发时间,以及更有才能和经验的开发人员来创建这样的设计,但是在长时间的运行生命周期中,这种方式因为其更方便维护以及更好的健壮性而使多付出的努力得到很好的回报。

一旦我们拥有了经过良好设计的、使用domain object的业务逻辑层,用户界面的开发就会非常容易了,它可以在很短的时间内由经验比较少的开发人员完成,所以在前面我们多花费的功夫在开发用户界面层的时候就得到了回报。下图显示了在数据访问层的接口和实体类、业务逻辑层的domain object之间的关系。你会看到,在业务逻辑层的Customer类有一个与在DAL中的CustomerDetails实体类相同的实例属性,只是多一个叫着CompleteName的,计算后的只读属性,该只读属性是通过组合FirstNameLastName后形成的。另外,它有一组实例方法以删除、更新由一个特定对象代表的数据,以及一组静态方法以提取出Customer对象的列表,或单个Customer对象的ID号,或创建、更新和删除一个customer。下面的例子代码显示一个用户界面开发人员如何使用一个包装了从数据库中提取出来的数据的customer对象,对其中一些字段进行更新,然后将修改存回数据库:

Customer cust = Customer.GetCustomerByID(3);

cust.FirstName = "Marco";

cust.LastName = "Bellinaso";

cust.Update();

数据访问层的接口和实体类、业务逻辑层的domain object之间的关系

在这个例子中,可以简单地使用UpdateCustomer静态方法而不需去创建类的实例:

Customer.UpdateCustomer(3, "Marco", "Bellinaso");

当然,你使用什么方法取决于当时的状况。如果你只需要对现有的记录进行更新而不需将在数据库中的当前数据装载一个Customer对象中,使用静态的方法。而当你需要读取和显示当前的数据,再更新数据,那么最开始的那个使用实例的方法则是最好的选择。

在我们为我们的系统设计业务逻辑层的时候,我们不仅仅要考虑有关层和类的设计。我们还需要考虑很多的问题:

  • 我们能否以及如何避免在很短的时间内重复查询相同的、未修改的数据,以增强性能并为用户提供良好的体验?
  • 我们如何为那些由很多子操作组成的,自动地执行以返回一致结果方法提供事务处理管理,以保证所有的子操作要么全部完成,要么都不完成而回到起始点?
  • 我们如何记录由系统产生的意外,以使管理员能在事后对其进行检查并帮助纠正错误;同时记录那些我们感兴趣的事件,不管是标准的ASP.NET事件(系统开始运行或关闭)或定制事件(数据库记录被删除)?

我在接下来的文章中将描述我对这些问题所做的思考,以及考虑采用的解决方案。Dec 21, 2008

使用缓存来得到更高的性能

在每一个网站或web应用程序中,总是存在不会经常变化的数据,很多用户会经常性地对这些数据发出请求。例如,文章分类列表、线上商店的商品目录和商品信息、国家和州列表等等。因此,提高网站性能最常用的解决方案就是部署一个缓存系统。把我们刚刚说到的那样一些数据放在缓存系统里,这样,当数据被某一请求使用后,该数据就会在内存中放一段时间,这样对该数据的下一个请求就会直接从内存中得到,而不再需要执行有关的查询语句并到数据库去走一趟。这就会大大节约处理时间和网络数据流量,因此很快就能得到结果。在ASP.NET 1.x中,经常使用System.Web.Caching.Cache类来实现数据缓存。该缓存使用经扩展的字典集合的方式工作,因此每个记录都有一个键值和对应的值。我们可以通过写Cach.Insert(“key”,data)来将某项目放入缓存中,并通过写data=Cache[“key”]来从缓存中提取数据。Cache类的Insert方法拥有众多的overload,由此,我们可以指定被缓存数据的失效时间或保存在缓存中的时间,以及是否具有可变化的时间定义(当被缓存数据每次被访问时,可变化的时间定义就会被清零重置)、再加上到其它文件或其它缓存项目依赖关系信息。当依赖的文件发生了变化,缓存的时间就到期了,这也会发生在缓存时间到了的情况下,这时缓存中的数据就会被销毁,而下一次用户对其进行请求时就会运行相关的查询语句,并将它再次保存在缓存系统中。

拥有SQL dependent支持的新缓存系统

ASP.NET 1.x缓存系统一个不足之处是当时间到期时,数据就会从缓存销毁,而我们必须重新从数据库得到它,即使在这段时间内数据并没有发生任何变化。相反,如果我们设置其缓存时间段为30分钟,而在数据缓存到缓存系统后不久就发生了变化,但是用户在这30分钟的时间内仍然得到的是旧的数据。这种情况对于某些类型的信息来讲是不可接受的,例如产品的价格和库存数量。在ASP.NET 2.0中,对Cache类进行了增强;现在它支持到数据库表的依赖关系描述,这是在到其它文件或其它缓存项目的依赖关系描述基础上新增加的。在实践中,我们可以为数据的缓存期定义一个模糊的时间,直到在数据库表中的系统发生了变化。这种使缓存失效的机制适用于所有的SQL Server数据库(7版本以后),而它是基于这些数据库的polling和触发器机制的。在SQL Server 2005中,增加了一个另一种类型的缓存失效机制,这种机制是基于从数据库接收事件而运作的,因此如果我们使用SQL Server 2005数据库系统的话,我们就可以实施和部署更加有效的缓存系统。另外,polling方法只支持在表这一层的变化,而在使用SQL Server 2005数据库系统的缓存系统中的事件方法可以使我们对表中的数据行进行监视。Dec 24-2008

SQL Server 7+ Support for Table-Level SQL Dependencies

当使用投票模式---polling style来决定缓存是否失效的方式时,ASP.NET 2.0会经常检查一张相关支持表中的一个计数器(时间间隔是可以配置的),如果得到的值大于以前得到的值,则会认为数据已被修改,并将之从缓存中移除。每一个我们想提供SQL-dependency支持的缓存都必然有一张对应的支持表,每一张支持表中都会有一个计数器(在AspNet_CacheTablesForChangeNotification支持表中的一条记录)。计数器由一个表中特定的触发器所增加。我们通过使用Visual Studio中的命令行,执行aspnet_regsql.exe命令行工具来创建所需要的表、触发器以及需要支持SQL-dependency系统的存贮过程。使用下面的命令在数据库添加有关支持,创建AspNet_CacheTablesForChangeNotification表以及支持的存贮过程:

aspnet_regsql.exe -E -S ./SqlExpress -d aspnetdb -ed

 

-E参数指明我们会使用集成的Windows安全机制,从而不需要传递用户名和密码(不然的话,我们就会使用-U-P参数来指明用户名和密码)。-S参数用来指定SQL Server数据库的实例名字。当我们安装了SQL Server 2005 Express以后,SqlExpree是默认的实例名。-d参数用来指定数据库的名字,而-ed则指示系统“enable database”

接下来,我们需要为一张特定的表添加缓存支持,这意味着我们必须在AspNet_CacheTablesForChangeNotification表中创建一条记录,并为我们想支持的表创建一个触发器。

aspnet_regsql.exe -E -S ./SqlExpress -d aspnetdb -t Customers –et

 

-t参数指定表的名字,而-et采参数则代表“enable table”。要使第一行命令起作用,aspnetdb数据库应该已经存在于数据库实例之中了。在SQL Server 7/2000SQL Server 2005的全功能版本中已经具备了上面说的条件。但是,因为使用SQL Server 2005 Express,我们会典型地在运行时间动态地将数据库装载到系统的数据库实例上去,这样我们就可以使用xcopy将数据库文件和其它的网站文件拷贝到主机上去以完成网站的部署。如果这是我们需要面临的状况,我们就必须先临时装载数据库文件,然后运行aspnet_regsql.exe命令,再将数据库文件卸载。而装载数据库文件和卸载数据库文件时我们需要使用sp_attach_dbsp_detach_db系统存贮过程。我们可以在SQL Server Management Studio Express中执行那两个存贮过程(可以从微软网站上下载),或者在Visual Studio 2005的命令行界面中运行sqlcmd.exe命令行程序。在本项目开发中,很多SQL命令都使用了sqlcmd程序,因为这个程序很容易获得。在Visual Studio命令行界面中用下面的命令来启动sqlcmd程序(其使用的参数含义与上面描述的相同):

 

sqlcmd -E -S ./SqlExpress

一旦启动了sqlcmd,我们使用下面的命令在装载数据库:

sp_attach_db "aspnetdb", "c:/Websites/TheBeerHouse/App_Data/AspNetDB.mdf"

go

然后,在上面命令行的另外一行运行我在前面讲的两条aspnet_regsql命令来结束此批处理命令,最后用下面的命令卸载数据库:

sp_detach_db "aspnetdb"

go

 

注意:使用quitexit并回车的方式来关闭sqlcmd程序。另外,如果你是从SQL Server Management Studio中运行那两个装载和卸载数据库的存贮过程的话,需要把双引号改成单引号。

完成SQL denpendency配置的最后一项工作是在web.config文件中写polling setting---投票设置。我们可以为相同的数据库配置不同的polling profiles,或者为不同的数据库配置不同设置。在system.web/caching/sqlCache dependency/databases区域下面添加如下的数据项即可:

<configuration>

   <connectionStrings>

 

      <add name="SiteDB" connectionString="Data Source=./SQLExpress;Integrated  

         Security=True;User Instance=True;AttachDBFilename=|DataDirectory|      

         AspNetDB.mdf" />

   </connectionStrings>

 

   <system.web>

      <caching>

         <sqlCacheDependency enabled="true" pollTime="10000">

            <databases>

               <add name="SiteDB-Cache" connectionStringName="SiteDB"

                  pollTime="2000" />

            </databases>

         </sqlCacheDependency>

      </caching>

 

      <!-- other settings here... -->

   </system.web>

</configuration>

我们可以看到,我们添加了一个数据项目叫着“SiteDB-cach”,此项目代表由连接串定义的数据库---SiteDB,并且我们还定义了polling的时间间隔为2(2000毫秒)。如果我们没有定义pollTime属性,系统就会使用默认的值---10秒。

好,到现在所有需要进行的配置准备都已经完成了。接下来的工作就是写一些代码来实现对数据的缓存:为Customers表创建一个dependency!为此,我们先创建System.Web.Caching.SqlCacheDependency类的一个实例,这个实例的构造器constructor需要我们在前面定义的caching profile名以及表名作为输入参数。然后,我们需要将数据插入Cach类,这个时候,我们将SqlCacheDependency对象作为Cache.Insert方法的第三个参数传递给它:

SqlCacheDependency dep = new SqlCacheDependency("SiteDB-cache", "Customers");

Cache.Insert("Customers", customers, dep);

我们假设在数据访问层中,我们创建了一个GetCustomers的方法,此方法返回CustomerDetails对象的列表,而CustomerDetails对象由Customers表中的数据填写。这样,我们可能象下面那样实施需要的缓存支持:Dec 30-2008

public List<CustomerDetails> GetCustomers() 注意:红色下划线部分是C#中的Generic的用法,即指定使用的类型)

{

   List<CustomerDetails> customers = null; //

 

   if (Cache["Customers"] != null)

   {

      customers = (List<CustomerDetails>)Cache["Customers"];

   }

   else

   {

      using (SqlConnection cn = new SqlConnection(_connString))

      {

         SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", cn);

         customers = FillCustomerListFromReader(cmd.ExecuteReader());

 

         SqlCacheDependency dep = new SqlCacheDependency(

            "SiteDB-cache", "Customers");

 

         Cache.Insert("Customers", customers, dep);

      }

   }

 

   return customers;

}

方法首先检查缓存中是否已经有数据:如果已经有了,直接从缓存中提取数据;如果不是那样,它先从数据库提取数据,然后再将提取的数据放入缓存中去。

你不仅可以为使用代码访问的存贮数据应用本缓存失效机制,你还可以将本缓存失效机制应用于ASP.NETOutput Caching特性,例如,缓存HTML页面,这样页面就不需每次都重新生成,甚至在页面的output不会改变的时候也是如此。要将output caching应用到页面,我们只需在.aspx文件顶部加上@OutputCache的页面directive即可(如果你想在用户空间上使用部分缓存的话,在.ascx文件中的相应部分加上前面的directive):

 

Not only can you use this caching expiration mechanism for storing data to be accessed from code, you can also use it for the ASP.NET's Output Caching feature, i.e., caching the HTML produced by page rendering, so that pages don't have to be re-rendered every time, even when the page's output would not change. To add output caching to a page, add the @OutputCache page directive at the top of the .aspx file (or the .ascx file if you want to use fragment caching in user controls):

<%@ OutputCache Duration="3600" VaryByParam="None"

   SqlDependency="SiteDB-cache:Customers" %>

With this directive, the page's output will be cached for a maximum of one hour, or less if the data in the Customers table is modified.

The problem with this implementation of the SQL dependency caching is that the dependency is to the entire table; it invalidates the cache regardless of which data in the table is changed. If you retrieved and cached just a few records from a table of thousands of records, why should you purge them when some other records are modified? With SQL Server 7 and 2000 whole-table monitoring for cache dependencies is your only choice, but SQL Server 2005 adds row-specific cache dependency tracking.

 

原创粉丝点击