这一章主要描述设计数据访问层时要注意的主要原则。它们覆盖了设计数据访问层遇到的通常问题及错误。下面的图表展示了数据层怎样嵌入一个通用的应用架构。(cnblog我的图片一直上传不了,报Remote server Error,只能使用网络图片了)
数据访问层组件
数据访问层组件。数据访问层组件将访问底层存储介质的必要方法抽象出来。将数据访问功能集中起来,可以使应用程式便于配置和维护。
  Data Helpers/Utilities。Helper和Utilities功能帮助操纵,转变和获取数据。他们由一些特别的类库组成以增强数据访问性能以及减少逻辑组件和服务代理的开发要求。
   服务代理。当业务组件必须将功能通过外部服务暴露时,你可能需要创建程式管理通信服务所需要的协议。服务代理将你的程式从调用不同的服务中分离出来,并且提供额外的服务,如服务的数据格式和你的应用程式需要的格式之间的映射。
正确的设计数据层可以减少程式部署后的开发和维护工作。这章会简要的概述一个设计访问层有效的方法。当设计数据层时使用下面的方式:
1. 创建一个总体的设计。
A. 确定数据源要求。
B. 确定数据访问方式。
C. 选择怎样映射数据结构到数据源。
D. 确认处理数据源错误时的策略。
E. 确认怎样连接数据源。
2. 设计你的数据访问层组件。
A. 列举你要访问的数据源。
B. 决定每一个数据源的访问方式。
C. 确认是否需要Hlper组件以简化数据访问组件开发和维护。
D. 确认相关的设计模式。例如,考虑使用表格数据,查询对象,Repository,以及其它模式。
3. 设计你的数据访问层Helper组件。
A. 识别可以从数据访问组件中拿出来通用的功能以重用。
B. 研究可用的Helper组件。
C. 考虑为连接字符串,数据源认证,监视以及异常处理设计自定义的Helper组件。
D. 考虑实现数据访问监视和测试你的Helper组件。
E. 考虑实现你的Helper组件安装和日志。
4. 设计服务代理。
A. 使用合适的工具以添加引用。生成一个代理和数据类来代表服务中的数据契约。
B. 确认服务怎样使用。对于大多应用程式来说,最好在业务层和数据访问层之间使用一个抽象层,这样会提供一个一致的接口而不考虑数据源。对于小的应用程式来说,业务层或者表示层,可以直接访问服务代理。
设计原则 下面的设计原则包括了设计数据层时应该考虑的不同方面。章节的剩余部分会提供给你选择合适的技术和设计模式的详细信息。
选择数据访问技术。合适的数据访问技术选择依赖于你处理的数据类型,以及你怎样操纵程式数据。特定的技术会适用特别的场景。下面的部分讨论了所选择的数据访问技术的好处以及缺点。
使用抽象以实现和数据访问层松耦合的接口。这个可以通过定义接口组件来完成,如众所周知的输入和输出Facade,可以将请求翻译为层组件可以理解的信息。另外,你可以定义接口或抽象基类,以被组件实现。
考虑稳固的数据结构。如果你要在数据访问层处理基于Table的实体,考虑使用DTO来帮助你把数据转换为统一的结构,DTO鼓励使用粗粒度的操作,以使数据在不同的边界层移动。
用数据访问层封装数据访问功能。数据访问层隐藏了访问数据源的详细信息,它用来管理连接,生成查询,以及映射程式实体到数据源结构。数据访问层的调用者使用自定义对象,Dataset,DataReader,以及XML文档等抽象结构来交互。其它应用程序层使用操作更复杂的方式来操纵数据以满足应用程式功能。分开的考虑可以帮助应用程式开发和维护。
决定怎样映射应用程序实体到数据源结构。应用程序使用的实体类型是最主要的决定因素。
决定你怎样来管理连接。实际上讲,数据访问层需要管理应用程序所有的数据源连接。你必须选择合适的方法去存储和保护连接信息以满足应用程序的安全需要。
决定你怎样处理数据异常。数据访问层应该捕获以及处理所有和数据源、CRUD操作引起的异常。如果只是数据异常,数据源访问及超时异常,数据访问层应该处理。如果错误影响应用程式的功能或响应的话,那异常应该被传递到其它层。
考虑安全风险。数据访问层应该防止数据偷窃和破坏,保护访问数据源的机制。应该使用“最少权限”的设计方法以限制需要执行的应用程式操作。如果数据源自己有能力限制权限的话,那数据访问层和数据源都要考虑安全。
减少数据包往返。考虑使用批处理命令以使用一个数据库操作。
考虑性能和客观上可测量性。可测量以及性能在设计数据访问层时就应该被考虑。例如,当设计一个基于网络的商业程式,数据层性能是程式的瓶颈。当数据访问层性能是决定性时,要限制昂贵的数据操作。
Category | Common Issues |
BLOB | 不适当的在数据库中存储BLOBS以替代文件系统。 |
对数据库中的BLOB数据使用了不正确的类型。 | |
搜索以及操作BLOB数据。 | |
Batching | 没有使用批处理以减少数据包往返。 |
Holding onto locks for excessive periods when batching. | |
没有考虑使用批处理策略以减少数据库数据包往返。 | |
Connections | 对连接池使用了不适当的配置。 |
没有处理连接超时和连接断掉。 | |
执行的事务跨越过个连接。 | |
在过长的时间内保持连接打开。 | |
使用单独的认证以代替全信任的子系统去访问数据库。 | |
Data Format | 选择了错误的数据格式。 |
没有考虑串行化需求。 | |
没有映射对象到关系数据存储。 | |
Excepion Management | 没有处理数据访问异常。 |
没有对调用者掩盖数据库异常。 | |
没有记录致命性的异常。 | |
Queries | 使用字符串连接来组建查询语句。 |
查询混合了业务逻辑。 | |
对于查询语句没有优化。 | |
Stored Procedures | 没有正确的将参数传给存储过程。 |
在存储过程里实现业务逻辑。 | |
没有考虑存储过程里的动态SQL可以导致性能,安全以及可维护性。 | |
Transactions | 使用不正确的隔离等级。 |
在多个数据源间使用了事务。 | |
使用了个别锁,可能导致死锁。 | |
允许长时间运行的事务导致锁住了数据访问。 | |
Validation | 对于数据字段没有使用数据类型验证。 |
没有处理NULL值。 | |
没有过滤不正确的字符。 | |
XML | 没有考虑怎样处理非常大的XML DataSet. |
没有选择合适的技术以方便XML和关系数据库交互。 | |
没有设置合适的索引导致沉重的XML查询负担。 | |
没有使用Schema来验证所有的XML输入。 |
BLOB是二进制的大型对象。如果数据以数据流的方式存储或检索,那它就是BLOB。BLOBs也许有结构,但显然不是数据库存储的结构或者数据访问层读写的结构。BLOB数据如果不能直接存储在数据库,则通常存储在文件系统中。BLOBs典型的使用方式是存储图像数据,但也经常用于存储以二进制为代表的对象。
当设计BLOBs策略时,考虑以下原则:当存储图像到硬盘不实际的时候,才会存储到数据库中。
在服务器间使用BLOBs来简化大型的二进制对象同步。
考虑你是否需要搜索BLOB数据。如果需要的话,创建其它数据库可搜索的字段来代替解析BLOB数据。
考虑为BLOB数据使用文件系统以提高性能。数据库的结构和实现决定着系统性能提升的高度。使用文件系统你还要考虑存储和同步数据库中的相关元数据。
当检索BLOB,把它转换成业务层或表现层需要操作的合适类型。
当使用缓冲传输的时候不要把BLOB数据存储到数据库中。
批处理
数据库批处理命令可以提高数据访问层的性能。在进入数据库执行环境前要消耗比较多的资源。批处理可以减少前端花费以及提高吞吐量和减少响应时间。对相似的查询使用批处理比较好,因为数据库对相似的查询会使用缓存以减少查询执行计划。
当设计批处理时,考虑以下原则:
使用批处理以减少数据库数据包往返以及减少网络流量。
为大量的相似查询建立批处理可以获得最大的效益。为不相似的或随即的查询建立批处理是无用的。
使用批处理和命令以及DataReader以加载或拷贝多个数据集。
当加载大量的基于文件的数据到数据库时,使用BULK
Copy工具。不要为长时间运行的批处理命令设置锁。
连接到数据源是数据访问层的基础。数据层必须管理所有的数据源连接。在数据层和数据源之间建立及管理连接都需要昂贵的资源。为了最大的提高性能,遵循下面的创建,管理和关闭连接原则。
选择一个策略以处理数据库连接字符串。
使用默认的连接池以减少活动的连接数量。
确定连接没有特别的不需要的字符。在效率不高的连接池里,多余的字符会使一些同样功能的连接获取到不同的值。
在每一个事务后不要打开或关闭连接,考虑在一定的时间里保持连接以允许重用。在部署前,要模拟真实的负载情况来测试你的应用程式,对需要大量时间处理的地方做出改变以优化性能。
不要以来垃圾回收器来释放连接。在明确的地方尽快释放它们。
在可能的地方使用单一的连接来执行事务。
设计Retry的机制以应付数据源丢失或连接超时的情况。
由于安全原因,避免使用系统名或用户数据源名(DSN)。
考虑在配置文件里对连接信息加密。例如,使用.net内置的机制加密连接字符串。
数据格式正确的数据格式用来合理的解释数据库的原始字节以形成数据层传递的信息。选择适当的数据格式可以提高程式间的互操作性,简化不同进程和机器间的串行化通信。数据格式和串行对于业务层存储和检索非常重要。
当设计数据格式时,考虑以下原则:
为程式选择合适的数据格式。
在很多情况下,你需要使用自定义的数据或业务实体来提高程式可维护性。这需要额外的代码来映射实体到数据库操作。O/RM解决方案可以减少大量的自写代码。
数据访问层使用的数据结构不能包含业务规则。
当使用经常改变的结构化数据时可以使用XML。
决定数据的串行化需求。
考虑互操作性需求。
异常管理
在数据库中设计统一的异常管理策略。如果可能,在数据库Helper组件中使用统一的异常处理逻辑。要特别注意在信任的层边界发生的异常。设计不能处理的异常策略以使敏感的应用程式信息不会暴露。
当设计异常管理策略时,考虑以下原则:
决定哪些异常可以呈现给调用者。死锁,连接问题和网络检测可以在数据层解决。
实现全局的异常处理以捕获不可知的异常以及将它抛给原调用者。
当通过服务接口来暴露数据层时,使用异常屏蔽信息以避免暴露敏感数据。
在数据层中处理所有数据访问相关的异常。
考虑对数据源错误和操作超时实现Retry机制。
告诉User异常会影响程式体验。考虑将异常信息传到业务层处理以及将它报告给User。
Log异常以容易的查找特定的错误。
如果有多个数据源,记录异常和错误的时候要包括单个数据源信息。
查询
查询是数据层主要的数据操作方式。它们将程式请求转换为Create,Retrieve,Update和Delete(CRUD)数据库操作。由于查询是很基本的东西,所以应该优化查询以最大的提高数据库性能和吞吐量。
当设计数据层查询,考虑以下的原则:
当存储过程不实用的时候使用SQL查询语句。
在SQL语句中使用参数以减少SQL注入。
当需要动态的创建查询,不要允许用户输入来决定查询的详细信息。
在数据层中不要使用字符串拼凑来创建动态查询。
使用对象来创建查询。例如,实现Query Object模式或使用ADO.NET对象。
存储过程
存储过程可以优化查询性能以及提高安全。它比SQL语句提供了更高的性能因为数据库可以针对它做出执行优化,并且可以根据存储过程里的语句来优化数据库。存储过程也提供了额外的安全,因为调用者可以从它获取数据而不用去数据库表或者视图里获取。存储过程同样可以参数化以支持多个应用程式的复杂查询。
当设计存储过程,考虑以下原则:
使用存储过程来提高数据库效率和数据层安全。
使用Output参数来返回单个的值。
避免动态SQL。当数据访问是程式的瓶颈时尽可能的使用存储过程。
对于单个的数据输入考虑使用单个的参数。
对于列表形式的数据考虑使用XML参数。
使用适当的事务来保持数据一致性。
设计合适的异常处理返回错误以被应用程式处理。
在存储过程里避免实现业务逻辑。
当处理数据时避免创建临时表。而且,在内存中创建、使用临时表比在磁盘中好。
事务
事务是将一系列的数据库操作作为一个原子单元联系起来以满足请求和保证数据库一致性。当所有动作的信息完成以及数据库的数据永久性更改代表了事务结束。事务可以在发生的错误的时候回滚数据库以保证数据库的ACID属性。     当设计事务时,考虑以下原则:    在需要的时候使用事务。例如,对于一个单独的SQL语句不需要使用事务,因为SQLSERVER可以自动将每一个SQL操作作为一个事务来执行。保持事务尽可能的短以减少锁住的时间。使用合适的隔离等级。数据一致性的平衡是很有争议的。高隔离级可以在并发的时候提供高的数据一致性。低隔离级可以通过降低一致性的花费而提高性能。
如果针对一个数据库执行事务,可以使用人工控制的事务。
如果一个单独的事务跨越了多个数据库,那你应该使用自动的事务。
避免混合人工控制和自动的事务。
如果使用人工控制的事务,考虑在存储过程里实现事务。
考虑在事务里使用MARS来加强并发以避免潜在的死锁问题。
如果执行多条Insert,Update和Delete,将他们在一个事务里处理以获取更好的性能。
验证设计一个有效的输入和数据验证策略对你的程式安全至关重要。决定从其它层,第三方组件,数据库或数据存储获取的数据验证规则。你可以验证任何在你信任的边界中通过的数据。
验证数据层中调用者发出的所有数据。
验证所有从数据库中检索的数据。
你可以验证任何在你信任的边界中通过的数据。
决定其它层的验证方式。如果数据已经是可信的数据,那没必要重复验证。
当设计验证时考虑数据输入的目的。
在执行数据库Update前验证所有的数据。
当验证失败的时候返回警告信息。
XMLXML在数据库外是维护数据结构的很有用的方式。由于性能原因,在大量数据集时小心使用XML。使用Schema去验证XML结构和内容。
当设计XML使用时,考虑以下原则:
使用XML读写器去访问XML格式的数据。
使用XML Schema来定义数据存储格式和传递。
用适当的Schema来验证接收到的XML。
在XML Schema里为复杂的数据参数使用自定义的验证。
用特定类型的列来存储XML,如果可能的话,使用数据库来获取最大的性能。
对于读Sqlserver里XML数据比较频繁的程式,考虑使用XML索引。
管理注意点当设计管理策略时,考虑以下原则:
仔细考虑你是否需要创建自定义的实体或者其它数据表示可以更好的满足需求。开发自定义的实体会加重开发负担。通常,自定义实体是为了方便开发者定制。
从一个基类派生业务实体可以提供基本的功能和封装通用的Task。
依靠为复杂数据使用内置的DataSet或XML Document来代替内部的集合,结构和其它编程结构。
实现一个通用的集合接口以从你的业务实体暴露通用的功能集合。
设计业务实体依赖于用于数据库交互的数据访问组件。统一实现数据访问策略和相关的业务逻辑。例如,如果你的业务实体直接访问SQLSERVER,那所有部署的程式客户端使用的业务实体都需要SQL连接和验证策略。
使用存储过程来从潜在的数据Shema中抽象数据访问。小心不要过度使用存储过程因为它会降低代码维护和重用,包括存储过程维护。过度使用的症状就是大量的存储过程互相调用。避免使用它们来实现控制流,操纵单个的值以及一些在T-SQL中难以实现的功能。
性能考量数据层设计及数据库设计都要考虑性能。在调节系统吞吐量的时候考虑以上两点。
当设计性能策略时,考虑以下原则:
考虑改变数据查询隔离级别。如果你在创建一个高吞吐量的程式,特殊的数据操作业务可能会在较低的隔离级执行。混合的隔离级别会影响系统数据的一致性,因此你要通过一个个实际例子小心的分析它。
考虑批处理命令以减少数据库的数据包往返。
使用连接池以及根据模拟实际场景来调节性能。
对非变化的数据使用并发来减轻数据库锁数据的花费。这可以避免锁住数据库行,以及因为锁一直打开的数据库连接。
如果更新数据只需要很少的时间,而且数据容易被更改,那尽量避免使用并发。长时间的锁越多,设计的可能性就越少。
使用DataReader来执行顺序的查找有助于提高性能。
安全考量数据层必须保护数据库免受偷窃或者破坏。同时也必须保护数据源可受访问。
当设计安全策略时,考虑以下原则:
当使用SqlServer时,考虑使用Windows验证而不是SQL验证。
考虑到性能问题,避免中间层来扮演此角色。
验证所有穿越可信任边界的数据。
如果使用SQL语句,考虑使用参数方法来代替字符串拼凑以防止SQL注入。
在配置文件里加密连接字符串以代替使用系统或DSN。
加密敏感数据或使用数据库加密来存储密码,使用Hash代替加密密码版本。
在网络或Internet传递时,加密敏感数据。
考虑在访问数据源中实现最少权限。
在数据层中要求调用者使用身份认证来审核。
记录登陆和身份认证信息。
部署考量 当部署数据访问层,软件架构师要考量生产环境中的性能和安全问题。
当部署数据访问层时,考虑以下原则:
把数据层和业务层放到同一个位置以提高程式性能。
如果你要支持一个远程的数据访问层,考虑使用TCP协议来提高性能。
你不能把数据访问层和数据库服务器放到一起。
模式映射
Category | Patterns |
General |
•
Active Record • Application Service • Domain Model • Layered Architecture • Transaction Script • Table Data Gateway • Repository |
Exception Management |
• Exception Shielding |
Transactions |
• Master-Master Replication • Coarse Grained Lock • Capture Transaction Details • Implicit Lock • Optimistic Offline Lock • Pessimistic Offline Lock • Transaction Script |
Active Record。 将数据访问层放置域对象中。
Capture Transactions Details。创建数据库对象,如触发器和记录表,来记录变化。
Repository。封装数据库和持久化操作中的对象集。
Data Transfer Object。使用数据对象来减少调用方法的次数。
Table Data Gateway。让sql统一访问一个表或视图来完成Select,Insert,Update和Delete操作。
技术考量当选择合适的数据层技术时,考虑以下原则:
使用客户端的数据库访问以提高性能。
考虑使用企业库模块来简化数据访问代码。
使用System.Xml和其子命名空间来操作XML格式的数据。
使用DataReader来呈现数据对基于asp.net的用户接口比较有益,DataReader是一个只读的,向前的读写器,读取每一行的速度很快。