.NET 数据访问体系结构指南
相关链接
Application Architecture for .NET:Designing Applications and Services
Alex Mackman、Chris Brooks、Steve Busby、Ed Jezierski、Jason Hogg、Roberta Leibovitz (Modeled Computation) 和 Colin Campbell (Modeled Computation)
Microsoft Corporation
摘要:本文提供有关在基于 .NET 的多层应用程序中实现基于 ADO.NET 的数据访问层的指南。它集中讨论一系列常见的数据访问任务和方案,并介绍了可帮助您选择最适合的方法和技术的指南。
简介
如果您要为基于 .NET 的应用程序设计数据访问层,应该使用 Microsoft?ADO.NET 作为数据访问模型。ADO.NET 功能丰富,支持松耦合的多层 Web 应用程序和 Web 服务的数据访问要求。像其他功能丰富的对象模型一样,ADO.NET 提供了多种方法来解决特定问题。
《.NET 数据访问体系结构指南》提供的信息可帮助您选择最适合的数据访问方法。它通过描述一系列广泛的常见数据访问方案、提供性能提示以及推荐最佳实施策略来达到此目的。本指南还提供了对常见问题的解答,这些问题包括:哪里是存储数据库连接字符串的最佳场所?如何实现连接池?如何处理事务?如何实现分页以使用户能够在大量记录中滚动?
本指南集中讨论如何使用 ADO.NET 并通过 SQL Server .NET 数据提供程序(ADO.NET 随附的两个数据提供程序之一)来访问 Microsoft SQL?Server?2000。在适当的时候,本指南将明确指出您在使用 OLE DB .NET 数据提供程序来访问其他支持 OLE DB 的数据源时应该了解的差异。
有关使用本文中讨论的指南和最佳实施策略开发的数据访问组件的具体实现,请参阅 Data Access Application Block。Data Access Application Block 包含该实现的源代码,您可以在基于 .NET 的应用程序中直接使用这些代码。
《.NET 数据访问体系结构指南》分为以下几个部分:
本页内容
| ADO.NET 简介 | |
| 管理数据库连接 | |
| 错误处理 | |
| 性能 | |
| 通过防火墙进行连接 | |
| 处理 BLOB | |
| 通过数据集执行数据库更新 | |
| 使用强类型数据集对象 | |
| 处理空数据字段 | |
| 事务处理 | |
| 数据分页 | |
| 附录 |
本文档的目标读者
本文档为需要生成基于 .NET 的应用程序的应用程序设计人员和企业开发人员提供指南。如果您负责设计和开发基于 .NET 的多层应用程序的数据层,请阅读本文档。
预备知识
要使用本指南来生成基于 .NET 的应用程序,您必须具有使用 ActiveX?Data Objects (ADO) 和/或 OLE DB 开发数据访问代码以及 SQL Server 方面的经验。您必须了解如何为 .NET 平台开发托管代码,并且必须了解 ADO.NET 数据访问模型引入的根本性的变化。有关 .NET 开发的详细信息,请参阅 http://msdn.microsoft.com/net。
新增功能
本文档已经进行了更新,以便包含有关执行数据集更新、使用类型化 DataSet 以及使用空数据字段的部分。
如正文中所指出的那样,本指南中的一些内容特别适用于 Microsoft Visual Studio?2003 开发系统和 .NET 框架 SDK 1.1 版。
下载《.NET 数据访问体系结构指南》
单击可从 MS.com 下载中心下载《.NET 数据访问体系结构指南》
ADO.NET 简介
ADO.NET 是基于 .NET 的应用程序的数据访问模型。可以使用它来访问关系数据库系统(如 SQL Server 2000、Oracle)和其他许多具有 OLE DB 或 ODBC 提供程序的数据源。在某种程度上,ADO.NET 代表 ADO 技术的最新进展。不过,ADO.NET 引入了一些重大变化和革新,旨在解决 Web 应用程序的松耦合特性以及在本质上互不关联的特性。有关 ADO 与 ADO.NET 的比较,请参阅 MSDN 文章“ADO.NET for the ADO Programmer”,网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/adonetprogmsdn.asp。
ADO.NET 引入的主要变化之一是用 DataTable、DataSet、DataAdapter 和 DataReader 对象的组合取代了 ADO Recordset 对象。DataTable 表示单个表中行的集合,在这一方面类似于 Recordset。DataSet 表示 DataTable 对象的集合,同时包括将各种表绑定在一起的关系和约束。实际上,DataSet 是带有内置 XML 支持的、内存中的关系结构。
DataSet 的主要特性之一是它不了解可能用来填充它的基础数据源。它是一个不连续的、独立的实体,用于表示数据集合,并且可以通过多层应用程序的不同层在组件之间传递。它还可以作为 XML 数据流进行序列化,这使其非常适合于在不同种类的平台之间进行数据传输。ADO.NET 使用 DataAdapter 对象将数据传送到 DataSet 和基础数据源,或者从数据源传出。DataAdapter 对象还提供以前与 Recordset 关联的增强的批量更新功能。
图 1 显示了完整的 DataSet 对象模型。

图 1.1.DataSet 对象模型
.NET 数据提供程序
ADO.NET 依赖于 .NET 数据提供程序的服务。这些提供程序提供对基础数据源的访问,并且包括四个主要对象(Connection、Command、DataReader 和 DataAdapter)。
目前,ADO.NET 随附了两类提供程序:Bridge 提供程序和 Native 提供程序。通过 Bridge 提供程序(如那些为 OLE DB 和 ODBC 提供的提供程序),可以使用为以前的数据访问技术设计的数据库。Native 提供程序(如 SQL Server 和 Oracle 提供程序)通常能够提供性能方面的改善,部分原因在于少了一个抽象层。
| • | SQL Server .NET 数据提供程序。这是一个用于 Microsoft SQL Server 7.0 和更高版本数据库的提供程序。它被进行了优化以便访问 SQL Server,并且它通过使用 SQL Server 的本机数据传输协议来直接与 SQL Server 进行通讯。 当您连接到 SQL Server 7.0 或 SQL Server 2000 时,请始终使用该提供程序。 |
| • | Oracle .NET 数据提供程序。用于 Oracle 的 .NET 框架数据提供程序通过 Oracle 客户端连接软件支持对 Oracle 数据源的数据访问。该数据提供程序支持 Oracle 客户端软件版本 8.1.7 及更高版本。 |
| • | OLE DB .NET 数据提供程序。这是一个用于 OLE DB 数据源的托管提供程序。它的效率要比 SQL Server .NET 数据提供程序稍微低一些,因为它在与数据库通讯时通过 OLE DB 层进行调用。请注意,该提供程序不支持用于开放式数据库连接 (ODBC) 的 OLE DB 提供程序 MSDASQL。对于 ODBC 数据源,请改为使用 ODBC .NET 数据提供程序(稍后将加以介绍)。有关与 ADO.NET 兼容的 OLE DB 提供程序的列表,请参阅 http://msdn.microsoft.com/library/en-us/cpguidnf/html/cpconadonetproviders.asp。 |
其他目前正处于测试阶段的 .NET 数据提供程序包括:
| • | ODBC .NET 数据提供程序。用于 ODBC 的 .NET 框架数据提供程序使用本机 ODBC 驱动程序管理器 (DM) 来支持借助于 COM 互操作性进行的数据访问。 |
| • | 用于从 SQL Server 2000 检索 XML 的托管提供程序。XML for SQL Server Web update 2(目前正处于测试阶段)包含一个托管提供程序,专门用于从 SQL Server 2000 中检索 XML。有关此更新的详细信息,请参阅 http://msdn.microsoft.com/library/default.asp?url=/nhp/default.asp?contentid=28001300。 |
有关不同数据提供程序的详细概述,请参阅《.NET 框架开发人员指南》中的“.NET 框架数据提供程序”,网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconadonetproviders.asp。
命名空间组织结构
与各个 .NET 数据提供程序相关联的类型(类、结构、枚举等)位于其各自的命名空间中:
| • | System.Data.SqlClient。包含 SQL Server .NET 数据提供程序类型。 |
| • | System.Data.OracleClient。包含 Oracle .NET 数据提供程序。 |
| • | System.Data.OleDb。包含 OLE DB .NET 数据提供程序类型。 |
| • | System.Data.Odbc。包含 ODBC .NET 数据提供程序类型。 |
| • | System.Data。包含独立于提供程序的类型,如 DataSet 和 DataTable。 |
在各自的关联命名空间内,每个提供程序都提供了对 Connection、Command、DataReader 和 DataAdapter 对象的实现。SqlClient 实现的前缀为“Sql”,而 OleDb 实现的前缀为“OleDb”。例如,Connection 对象的 SqlClient 实现是 SqlConnection,而 OleDb 实现则为 OleDbConnection。同样,DataAdapter 对象的两个实现分别为 SqlDataAdapter 和 OleDbDataAdapter。
在本指南中,所用示例取自 SQL Server 对象模型。尽管此处未加说明,Oracle/OLEDB 和 ODBC 中提供了类似的功能。
一般编程
如果您可能要面向不同的数据源,并且需要将您的代码从一个数据源移至另一个数据源,请考虑编程以支持 System.Data 命名空间中的 IDbConnection、IDbCommand、IDataReader 和 IDbDataAdapter 接口。Connection、Command、DataReader 和 DataAdapter 对象的所有实现都必须支持这些接口。
有关实现 .NET 数据提供程序的详细信息,请参阅 http://msdn.microsoft.com/library/en-us/cpguidnf/html/cpconimplementingnetdataprovider.asp。
还应该注意,如果应用程序使用单对象模型来访问多个数据库,则 OLE DB 和 ODBC 桥接提供程序都可以使用。在此情况下,需要考虑与应用程序的性能需要相比,要求应用程序具有多大的灵活性,以及在何种程度上需要数据库特有的功能。
图 2 阐明了数据访问栈,并说明了 ADO.NET 与其他数据访问技术(包括 ADO 和 OLE DB)之间的关系。它还说明了 ADO.NET 模型内的两个托管提供程序和主要对象。

图 1.2.数据访问栈
有关 ADO 向 ADO.NET 的演变的详细信息,请参阅文章“Introducing ADO+:Data Access Services for the Microsoft .NET Framework”(MSDN Magazine,2000 年 11 月号),网址为:http://msdn.microsoft.com/msdnmag/issues/1100/adoplus/default.aspx。
存储过程与直接 SQL
本文档中显示的大多数代码片段使用 SqlCommand 对象来调用存储过程,以执行数据库操作。在某些情况下,您将不会看到 SqlCommand 对象,因为存储过程名被直接传递给 SqlDataAdapter 对象。在内部,这仍然会导致创建 SqlCommand 对象。
您应该使用存储过程而不是嵌入的 SQL 语句,原因如下:
| • | 存储过程通常可以改善性能,因为数据库能够优化存储过程使用的数据访问计划,并且能够缓存该计划以供将来重用。 |
| • | 可以在数据库内分别设置各个存储过程的安全保护。客户端不必对基础表拥有访问权限,就可以获得执行存储过程的权限。 |
| • | 存储过程可以简化维护工作,因为修改存储过程通常要比更改已部署组件中的硬编码 SQL 语句容易。 |
| • | 存储过程为基础数据库架构增加了额外的抽象级别。存储过程的客户端与存储过程的实现细节是彼此隔离的,与基础架构也是彼此隔离的。 |
| • | 存储过程可以减少网络流量,因为可以批量执行 SQL 语句,而不是从客户端发送多个请求。 |
SQL Server 联机文档强烈建议您不要使用“sp_”作为名称前缀来创建任何存储过程,因为此类名称已经被指定给系统存储过程。SQL Server 始终按以下顺序来查找以 sp_ 开头的存储过程:
| 1. | 在主数据库中查找存储过程。 |
| 2. | 基于所提供的任何限定符(数据库名或所有者)来查找存储过程。 |
| 3. | 使用 dbo 作为所有者来查找存储过程(如果未指定所有者)。 |
属性与构造函数参数
可以通过构造函数参数来设置 ADO.NET 对象的特定属性值,也可以直接设置属性值。例如,下面的代码片段在功能上是等效的。
// Use constructor arguments to configure command objectSqlCommand cmd = new SqlCommand( "SELECT * FROM PRODUCTS", conn );// The above line is functionally equivalent to the following// three lines which set properties explicitlysqlCommand cmd = new SqlCommand();cmd.Connection = conn;cmd.CommandText = "SELECT * FROM PRODUCTS";
从性能角度看,这两种方法之间的差异是微不足道的,因为针对 .NET 对象设置和获取属性要比针对 COM 对象执行类似操作更为高效。
选择哪种方法取决于个人喜好和编码风格。不过,对属性进行明确设置确实能够使代码更易理解(尤其是当您不熟悉 ADO.NET 对象模型时)和调试。
注 过去,Microsoft Visual Basic? 开发系统的开发人员被告诫避免使用“Dim x As New…”构造来创建对象。在 COM 领域里,上述代码会导致 COM 对象创建过程出现短路现象,从而带来一些微小和重大的错误。然而,在 .NET 领域里,这不再是一个问题。
管理数据库连接
数据库连接代表一种关键的、昂贵的和有限的资源,尤其是在多层 Web 应用程序中。正确地管理连接是十分必要的,因为您采取的方法可能显著影响应用程序的总体可伸缩性。同时,还要认真考虑在何处存储连接字符串。需要使用可配置的且安全的位置。
在管理数据库连接和连接字符串时,应该努力做到:
| • | 通过在多个客户端中多路复用数据库连接池,帮助实现应用程序的可伸缩性。 |
| • | 采用可配置的、高性能的连接池策略。 |
| • | 在访问 SQL?Server 时使用 Windows 身份验证。 |
| • | 在中间层避免模拟。 |
| • | 安全地存储连接字符串。 |
| • | 尽量晚地打开数据库连接,尽量早地将其关闭。 |
本节讨论连接池,并且帮助您选择适当的连接池策略。本节还将考虑应该如何管理、存储和操纵数据库连接字符串。最后,本节将给出两种编码模式,可用来帮助确保连接被可靠地关闭,并被返回到连接池。
使用连接池
通过数据库连接池,应用程序可以重用池中现有的连接,而不必反复与数据库建立新的连接。该技术可显著提高应用程序的可伸缩性,因为有限数量的数据库连接可以为数量大得多的客户端提供服务。同时,由于可以节省建立新连接所需的大量时间,该技术还能够改善性能。
像 ODBC 和 OLE DB 这样的数据访问技术提供了多种形式的连接池,可以在不同程度上进行配置。这两种方法对于数据库客户端应用程序而言在很大程度上是透明的。OLE DB 连接池经常被称为会话或资源池。
有关 Microsoft Data Access Components (MDAC) 内部的池机制的一般性讨论,请参阅“Pooling in the Microsoft Data Access Components”,网址为 http://msdn.microsoft.com/library/en-us/dnmdac/html/pooling2.asp。
ADO.NET 数据提供程序提供了透明的连接池,这些连接池的确切技术细节对于各个提供程序而言是不同的。本节针对下列提供程序来讨论连接池:
| • | SQL Server .NET 数据提供程序 |
| • | Oracle .NET 数据提供程序 |
| • | OLE DB .NET 数据提供程序 |
| • | ODBC .NET 数据提供程序 |
SQL Server .NET 数据提供程序的池机制
如果您使用的是 SQL Server .NET 数据提供程序,请使用该提供程序提供的连接池支持。这是一种由该提供程序在内部实现的支持事务处理并且非常高效的机制,它存在于托管代码中。池是以每个应用程序的域为基础创建的,并且在应用程序域卸载之前不会销毁。
可以透明地使用这种形式的连接池,但应该知道池的管理方式以及可用来微调连接池的各种配置选项。
在许多情况下,对于您的应用程序而言,SQL Server .NET 数据提供程序的默认连接池设置可能已经足够了。在开发和测试基于 .NET 的应用程序的过程中,建议您对规划通信模式进行模拟,以确定是否需要修改连接池大小。
需要生成可伸缩的高性能应用程序的开发人员应该最大限度地减少使用连接的时间,只在检索或更新数据时才使连接保持打开状态。连接关闭时,将被返回到连接池,并可供重用。在此情况下,到数据库的实际连接不会被切断;不过,如果连接池被禁用,则到数据库的实际连接将被关闭。
开发人员应该十分小心,不要依赖垃圾回收器来释放连接,因为当引用离开作用范围时,连接未必能够关闭。这是连接泄漏的一种常见根源,当请求新连接时,这会导致连接异常。
配置 SQL Server .NET 数据提供程序连接池
可以使用一组名称-值对(通过连接字符串提供)来配置连接池。例如,可以配置是否启用连接池(默认情况下启用)、池的最大容量和最小容量,以及要打开连接的排队请求可以阻塞的时间长度。下面是一个示例连接字符串,用于配置池的最大容量和最小容量。
"Server=(local); Integrated Security=SSPI; Database=Northwind; Max Pool Size=75; Min Pool Size=5"
打开连接并创建池以后,会将多个连接添加到池中,以便将连接数量提高到所配置的最小数量。随后,可以继续向该池中添加连接,直至达到所配置的最大池数量。当达到最大数量时,要打开连接的新请求将排队等待一段可配置的时间。
选择池大小
对于管理成千上万个客户端的并发请求的大规模系统而言,能够建立最大值阈值是非常重要的。您需要监控连接池和应用程序的性能,以便确定系统的最佳池大小。最佳大小还取决于用来运行 SQL Server 的硬件。
在部署过程中,您可能希望减小默认的最大池大小(目前为 100)以帮助查找连接泄漏。
如果您设立最小池大小,将会产生一点性能开销,因为在开始时对池进行填充以使其达到该尺寸,尽管头几个进行连接的客户端会因此受益。注意,创建新连接的过程是按顺序执行的,这意味着在最初填充池时,服务器不会被同时到来的大量请求所淹没。
有关监控连接池的详细信息,请参阅本文档中的监控连接池一节。有关连接池连接字符串关键字的完整列表,请参阅《.NET 框架开发人员指南》中的“Connection Pooling for the .NET Framework Data Provider for SQL Server”一节,网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconconnectionpoolingforsqlservernetdataprovider.asp。
更多信息
当使用 SQL Server .NET 数据提供程序连接池时,请注意以下几个方面:
| • | 连接是通过连接字符串上的完全匹配算法进行池化的。池机制甚至对名称-值对之间的空格也敏感。例如,下面的两个连接字符串将导致两个独立的池,因为第二个连接字符串包含额外的空格字符。 SqlConnection conn = new SqlConnection( "Integrated Security=SSPI;Database=Northwind");conn.Open(); // Pool A is createdSqlConmection conn = new SqlConnection( "Integrated Security=SSPI ; Database=Northwind");conn.Open(); // Pool B is created (extra spaces in string) |
| • | 连接池被划分为多个事务专有池和一个与当前尚未在事务中登记的连接对应的池。对于与特定事务上下文关联的线程,会返回相应池(该池包含在该事务中登记的连接)的连接。这就使得使用已登记的连接成为一个透明的过程。 |
OLE DB .NET 数据提供程序的池机制
OLE DB .NET 数据提供程序通过使用基础 OLE DB 资源池来池化连接。有多个用于配置资源池的选择:
| • | 可以使用连接字符串来配置、启用或禁用资源池。 |
| • | 可以使用注册表。 |
| • | 可以用编程方式配置资源池。 |
为避免出现与注册表相关的部署问题,请不要使用注册表来配置 OLE DB 资源池。
有关 OLE DB 资源池的详细信息,请参阅《OLE DB 程序员参考》第 19 章“OLE DB Services”中的“Resource Pooling”,网址为:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/oledb/htm/olprcore_chapter19.asp。
使用池对象管理连接池
作为 Windows DNA 开发人员,鼓励您禁用 OLE DB 资源池和/或 ODBC 连接池,并使用 COM+ 对象池作为池化数据库连接的技术。这有两个主要原因:
| • | 池大小和阈值可以明确的配置(在 COM+ 目录中)。 |
| • | 性能得到改善。池对象方法的性能比本机池高 50%。 |
然而,因为 SQL Server .NET 数据提供程序在内部使用池机制,所以您不再需要开发自己的对象池机制(在使用该提供程序时)。因此,您可以避免执行与手动事务登记相关联的复杂任务。
如果您使用的是 OLE DB .NET 数据提供程序,您可能需要考虑使用 COM+ 对象池,以便充分利用卓越的配置和改善的性能。如果您为此目的开发池对象,则必须禁用 OLE DB 资源池和自动事务登记(例如,通过在连接字符串中包含 "OLE DB Services=-4")。您必须在自己的池对象实现中处理事务登记。
监控连接池
要对应用程序使用连接池的情况进行监控,可以使用 SQL Server 随附的事件探查器工具,或者使用 Microsoft Windows? 2000 操作系统随附的性能监视器工具。
使用 SQL Server 事件探查器监控连接池
| 1. | 单击 Start,指向 Programs,指向 MicrosoftSQLServer,然后单击 Profiler 以启动事件探查器。 |
| 2. | 在 File 菜单上,指向 New,然后单击 Trace。 |
| 3. | 提供连接详细信息,然后单击 OK。 |
| 4. | 在 Trace Properties 对话框中,单击 Events 选项卡。 |
| 5. | 在 Selected event classes 列表中,确保 Audit Login 和 Audit Logout 事件显示在 Security Audit 下面。要使跟踪变得更为清晰,请从该列表中删除所有其他事件。 |
| 6. | 单击 Run 以启动跟踪。当连接建立时,您将看到 Audit Login 事件;当连接关闭时,您将看到 Audit Logout 事件。 |
使用性能监视器监控连接池
| 1. | 单击 Start,指向 Programs,指向 Administrative Tools,然后单击 Performance 以启动性能监视器。 |
| 2. | 右键单击图形背景,然后单击 AddCounters。 |
| 3. | 在 Performance object 下拉列表中,单击 SQL Server:General Statistics。 |
| 4. | 在显示的列表中,单击 User Connections。 |
| 5. | 单击 Add,然后单击 Close。 |
管理安全性
尽管数据库连接池提高了应用程序的总体可伸缩性,但这意味着您不再能够在数据库级别管理安全性。这是因为,要支持连接池,连接字符串必须完全相同。如果您需要跟踪每个用户的数据库操作,请考虑添加一个参数,以便能够传递用户标识并在数据库中手动记录用户操作。您需要将该参数添加到每个操作中。
使用 Windows 身份验证
在连接到 SQL Server 时,应该使用 Windows 身份验证,因为它提供了许多好处:
| 1. | 安全性更易于管理,因为您使用单一 (Windows) 安全模型,而不是独立的 SQL Server 安全模型。 |
| 2. | 可避免将用户名和密码嵌入到连接字符串中。 |
| 3. | 不会以明文方式通过网络传递用户名和密码。 |
| 4. | 通过采用密码到期期限、最小长度以及在多次无效登录请求后锁定帐户,改善了登录安全性。 |
更多信息
在使用 Windows 身份验证来访问 SQL Server 时,请遵循以下指导原则:
| • | 考虑性能折衷。性能测试已经表明,在使用 Windows 身份验证打开池数据库连接时,速度要比使用 SQL Server 身份验证慢。.NET 运行库 1.1 版已经降低了 SQL Server 安全性能超过 Windows 身份验证的幅度,但 SQL Server 身份验证仍然要快一些。 然而,尽管 Windows 身份验证的开销仍然比较高,但与执行命令或存储过程所花的时间相比,性能上的降低相对而言是可以忽略的。因此,在大多数情况下,使用 Windows 身份验证在安全方面的好处胜过了性能略有下降所带来的坏处。在做决定之前,请对应用程序的性能要求进行评估。 | ||||||
| • | 在中间层避免模拟。Windows 身份验证要求有 Windows 帐户以便进行数据库访问。尽管在中间层使用模拟似乎是合理的,但请避免这样做,因为这样会使连接池失效,并且对应用程序可伸缩性产生严重的影响。 要解决该问题,请考虑模拟有限数量的 Windows 帐户(而不是已验证身份的用户),每个帐户都代表一个特定的角色。 例如,您可以使用以下方法:
| ||||||
| • | 对网络库使用 TCP/IP。SQL Server 7.0 及更高版本为所有网络库提供 Windows 身份验证支持。使用 TCP/IP 可获得配置、性能和可伸缩性方面的好处。有关使用 TCP/IP 的详细信息,请参阅本文档中的通过防火墙连接一节。 |
有关开发安全的 ASP.NET 和 Web 应用程序的一般性指导,请参阅下面的 Microsoft patterns & practices 指南:
| • | 卷 I,《生成安全的 ASP.NET 应用程序:身份验证、授权和安全通讯》(网址为 http://www.microsoft.com/practices) |
| • | 卷 II,《提高 Web 应用程序安全性:威胁和对策》(将发布于以下网址:http://www.microsoft.com/practices) |
存储连接字符串
要存储数据库连接字符串,可以有多种选择,这些选择具有不同级别的灵活性和安全性。尽管在源代码中对连接字符串进行硬编码可提供最佳性能,但文件系统缓存可确保在外部将该字符串存储到文件系统中所带来的性能下降是微不足道的。几乎在所有情况下,人们都首选外部连接字符串所提供的额外的灵活性(它支持管理员配置)。
当您选择连接字符串存储方法时,需要注意的两个最重要的事项是安全性和配置简易性,然后紧跟着的是性能。
可以选择下列位置来存储数据库连接字符串:
| • | 在应用程序配置文件中;例如,ASP.NET Web 应用程序的 Web.config |
| • | 在通用数据链接 (UDL) 文件中(仅由 OLE DB .NET 数据提供程序支持) |
| • | 在 Windows 注册表中 |
| • | 在自定义文件中 |
| • | 在 COM+ 目录中,方法是使用构建字符串(仅适用于服务组件) |
通过使用 Windows 身份验证来访问 SQL Server,可以避免将用户名和密码存储在连接字符串中。如果您的安全要求需要采取更严格的措施,请考虑以加密格式存储连接字符串。
对于 ASP.NET Web 应用程序而言,在 Web.config 文件内以加密格式存储连接字符串,代表着一种安全的、可配置的解决方案。
注 可以在连接字符串中将 Persist Security Info 命名值设置为 false,以禁止通过 SqlConnection 或 OleDbConnection 对象的 ConnectionString 属性返回对安全敏感的细节(如密码)。
下面几小节讨论了如何使用各种选择来存储连接字符串,并介绍了各种方法的相对优点和缺点。这些内容有助于您根据自己特定的应用程序方案做出明智的选择。
注 通过“配置应用程序管理”块,可以管理从数据库连接到复杂层次结构数据的各种配置设置。有关详细信息,请参阅 http://msdn.microsoft.com/practices。
使用 XML 应用程序配置文件
可以使用 <appSettings> 元素在应用程序配置文件的自定义设置节中存储数据库连接字符串。该元素支持任意的密钥-值对,如以下代码片段所示:
<configuration> <appSettings> <add key="DBConnStr" value="server=(local);Integrated Security=SSPI;database=northwind"/> </appSettings></configuration>
注 <appSettings> 元素出现在 <configuration> 元素下面,并且不是紧跟在 <system.web> 的后面。
优点
| • | 易于部署。连接字符串是通过定期 .NET xcopy 部署与配置文件一起部署的。 |
| • | 易于以编程方式访问。通过 ConfigurationSettings 类的 AppSettings 属性,可以在运行时方便地读取已配置的数据库连接字符串。 |
| • | 支持动态更新(仅限于 ASP.NET)。如果管理员在 Web.config 文件中更新连接字符串,当下一次访问该字符串时(对于无状态组件而言,这可能是客户端下一次使用该组件进行数据访问请求),所做更改将生效。 |
缺点
| • | 安全性。尽管 ASP.NET Internet 服务器应用程序编程接口 (ISAPI) 动态链接库 (DLL) 禁止客户端直接访问带有 .config 文件扩展名的文件,并且可以使用 NTFS 权限进一步限制访问,您可能仍然希望避免以明文形式在前端 Web 服务器上存储这些详细信息。为获得额外的安全性,请以加密格式在配置文件中存储连接字符串。 |
更多信息
| • | 可以使用 System.Configuration.ConfigurationSettings 类的静态 AppSettings 属性来检索自定义应用程序设置。以下代码片段对此进行了说明,该代码片段采用了前面例举的名为 DBConnStr 的自定义密钥: using System.Configuration;private string GetDBaseConnectionString(){ return ConfigurationSettings.AppSettings["DBConnStr"];} |
| • | 有关配置 .NET 框架应用程序的详细信息,请参阅 http://msdn.microsoft.com/library/en-us/cpguidnf/html/cpconconfiguringnetframeworkapplications.asp。 |
使用 UDL 文件
OLE DB .NET 数据提供程序在其连接字符串中支持通用数据链接 (UDL) 文件名。您可以通过使用 OleDbConnection 对象的构建参数来传递连接字符串,或者通过使用该对象的 ConnectionString 属性来设置连接字符串。
注 SQL Server .NET 数据提供程序在其连接字符串中不支持 UDL 文件。因此,只有在您使用 OLE DB .NET 数据提供程序时,才可使用该方法。
对于 OLE DB 提供程序,要通过连接字符串引用 UDL 文件,请使用“File Name=name.udl”。
优点
| • | 标准方法。您可能已在使用 UDL 文件进行连接字符串管理。 |
缺点
| • | 性能。每次打开连接时,都要读取和分析含有 UDL 的连接字符串。 |
| • | 安全性。UDL 文件以纯文本形式存储。您可以通过使用 NTFS 文件权限来确保这些文件的安全,但这样做可能带来与 .config 文件相同的问题。 |
| • | SqlClient 不支持 UDL 文件。SQL Server .NET 数据提供程序不支持该方法。该提供程序用于访问 SQL Server 7.0 及更高版本。 |
更多信息
| • | 要支持管理,请确保管理员对 UDL 文件具有读/写访问权限,并且用于运行应用程序的身份具有读访问权限。对于 ASP.NET Web 应用程序,应用程序辅助进程在默认情况下通过使用 SYSTEM 帐户来运行,尽管您可以通过使用计算机全局配置文件 (Machine.config) 的 <processModel> 元素来覆盖这一配置。您还可以通过使用 Web.config 文件的 <identity> 元素进行模拟(可随意使用指定帐户)。 |
| • | 对于 Web 应用程序,请确保不要将 UDL 文件放在虚拟目录中,否则会使该文件可通过 Web 下载。 |
| • | 有关上述功能及其他与安全相关的 ASP.NET 功能的详细信息,请参阅“Authentication in ASP.NET:.NET Security Guidance”,网址为 http://msdn.microsoft.com/library/en-us/dnbda/html/authaspdotnet.asp。 |
使用 Windows 注册表
您还可以在 Windows 注册表中使用自定义键来存储连接字符串,尽管由于部署问题不鼓励这样做。
优点
| • | 安全性。可以通过使用访问控制列表 (ACL) 来管理对选定注册表键的访问。要获得更高级别的安全性,请考虑将数据加密。 |
| • | 易于以编程方式访问。可以使用 .NET 类来支持从注册表中读取字符串。 |
缺点
部署。必须与应用程序一起部署相关注册表设置,这在一定程度上抵消了 xcopy 部署的优点。
使用自定义文件
可使用自定义文件来存储连接字符串。然而,该技术没有任何优点,建议不要使用该技术。
优点
| • | 无。 |
缺点
| • | 额外的编码。该方法需要完成额外的编码工作,并迫使您显式处理并发问题。 |
| • | 部署。该文件必须与其他 ASP.NET 应用程序文件一起复制。应避免将该文件放在 ASP.NET 应用程序目录或子目录中,以防止它通过 Web 被下载。 |
使用构建参数和 COM+ 目录
可以在 COM+ 目录中存储数据库连接字符串,并通过对象构建字符串将其自动传递给您的对象。COM+ 将在实例化该对象后立即调用该对象的 Construct 方法,同时提供所配置的构建字符串。
注 该方法仅对服务组件有效。仅当您的托管组件使用其他服务(如分布式事务处理支持或对象池)时,才应考虑该方法。
优点
| • | 管理。管理员可以通过使用“组件服务”MMC 管理单元方便地配置连接字符串。 |
缺点
| • | 安全性。COM+ 目录被视为不安全的存储区域(尽管您可以通过 COM+ 角色来限制访问权限),因而不得用来以明文形式保存连接字符串。 |
| • | 部署。COM+ 目录中的项必须与基于 .NET 的应用程序一起部署。如果您正在使用其他企业服务,如分布式事务处理或对象池,则在该目录中存储数据库连接字符串不会带来任何额外的部署开销,因为必须部署 COM+ 目录以支持那些其他服务。 |
| • | 组件必须接受服务。您只能对服务组件使用构建字符串。不应简单地从 ServicedComponent 中派生组件的类(使您的组件接受服务)来启用构建字符串。 重要说明:确保连接字符串的安全性至关重要。对于 SQL 身份验证,连接字符串包含用户名和密码。如果攻击者利用 Web 服务器上的源代码漏洞,并且获取了访问配置存储的权限,则数据库将容易受到攻击。要避免这一问题,应该将连接字符串加密。有关可用于加密明文连接字符串的不同方法的说明,请参阅“Improving Web Application Security:Threats and Countermeasures”(该文章将发布在 http://www.microsoft.com/practices)。 |
更多信息
| • | 有关如何配置 .NET 类以便构建对象的详细信息,请参阅附录中的如何启用 .NET 类的对象构造。 | ||||
| • | 有关开发服务组件的详细信息,请参阅 http://msdn.microsoft.com/library/en-us/cpguidnf/html/cpconwritingservicedcomponents.asp。 | ||||
| • | 有关开发安全的 ASP.NET 和 Web 应用程序的一般性指导,请参阅下面的 Microsoft patterns & practices 指南:
|
连接使用模式
无论您使用哪种 .NET 数据提供程序,您都必须始终遵循下列原则:
| • | 尽可能晚地打开数据库连接。 |
| • | 以尽可能短的时间使用连接。 |
| • | 尽可能早地关闭连接。在通过 Close 或 Dispose 方法关闭连接之前,不会将连接返回到池中。即使您检测到连接已经进入断开状态,也应该关闭该连接。这可以确保将该连接返回到池中,并将其标记为无效连接。对象池程序定期扫描池,查找那些已被标记为无效的对象。 |
要保证在方法返回之前关闭连接,请考虑使用下面的两个代码示例中阐明的方法之一。第一个方法使用 finally 块。第二个方法使用 C# using 语句,它确保调用对象的 Dispose 方法。
以下代码确保 finally 块关闭连接。注意,该方法对 Visual Basic .NET 和 C# 都有效,因为 Visual Basic .NET 支持结构化异常处理。
public void DoSomeWork(){ SqlConnection conn = new SqlConnection(connectionString); SqlCommand cmd = new SqlCommand("CommandProc", conn ); cmd.CommandType = CommandType.StoredProcedure; try { conn.Open(); cmd.ExecuteNonQuery(); } catch (Exception e) { // Handle and log error } finally { conn.Close(); }} 以下代码显示了一个备选方法,该方法使用了 C# using 语句。注意,Visual Basic .NET 不提供 using 语句或任何等效功能。
public void DoSomeWork(){ // using guarantees that Dispose is called on conn, which will // close the connection. using (SqlConnection conn = new SqlConnection(connectionString)) { SqlCommand cmd = new SqlCommand("CommandProc", conn); fcmd.CommandType = CommandType.StoredProcedure; conn.Open(); cmd.ExecuteQuery(); }} 您还可以将该方法应用于其他必须关闭才能使用当前连接执行其他任何任务的对象,例如 SqlDataReader 或 OleDbDataReader。
错误处理
ADO.NET 错误是通过 .NET 框架所固有的基础结构化异常处理支持来产生和处理的。因此,在数据访问代码内部处理错误的方法与在应用程序的其他地方一样。可以通过标准的 .NET 异常处理语法和技术来检测和处理异常。
本节向您说明如何开发健壮的数据访问代码,并解释如何处理数据访问错误。同时,本节还提供了与 SQL Server .NET 数据提供程序相关的具体异常处理指导。
.NET 异常
.NET 数据提供程序将数据库特有的错误状况转换为标准异常类型,供您在自己的数据访问代码中进行处理。数据库特有的错误细节是通过相关异常对象的属性提供给您的。
所有 .NET 异常类型归根结底都是从 System 命名空间中的 Exception 类派生出来的。.NET 数据提供程序引发提供程序特有的异常类型。例如,每当 SQL Server 返回错误状况时,SQL Server .NET 数据提供程序都会引发 SqlException 对象。类似地,OLE DB .NET 数据提供程序引发 OleDbException 类型的异常,它含有基础 OLE DB 提供程序所公开的细节。
图 3 显示了 .NET 数据提供程序异常层次。注意,OleDbException 类是从 ExternalException(它是所有 COM Interop 异常的基类)派生的。该对象的 ErrorCode 属性存储了由 OLE DB 产生的 COM HRESULT。

图 1.3..NET 数据提供程序异常层次
捕获和处理 .NET 异常
要处理数据访问异常状况,请将数据访问代码放在 try 块内部,并使用 catch 块通过适当的筛选器来捕获产生的任何异常。例如,当使用 SQL Server .NET 数据提供程序来编写数据访问代码时,应该捕获 SqlException 类型的异常,如以下代码所示:
try{ // Data access code}catch (SqlException sqlex) // more specific{}catch (Exception ex) // less specific{} 如果您提供多个具有不同筛选条件的 catch 语句,请记住将这些语句按其类型的特殊程度从高到低排序。这样,对于任何给定的异常类型,都会执行最特殊类型的 catch 块。
该 SqlException 类公开了含有异常状况细节的属性。这些属性包括:
| • | Message 属性,它包含描述错误的文本。 |
| • | Number 属性,它包含唯一标识错误类型的错误号。 |
| • | State 属性,它包含有关错误的调用状态的附加信息。它通常用于指示特定错误状况的具体出现位置。例如,如果单个存储过程能够在多行产生同一错误,则应该使用状态来标识错误的具体出现位置。 |
| • | Errors 集合,它包含有关 SQL Server 产生的错误的详细错误信息。Errors 集合将始终至少包含一个 SqlError 类型的对象。 |
以下代码片段阐明了如何使用 SQL Server .NET 数据提供程序来处理 SQL Server 错误状况:
using System.Data;using System.Data.SqlClient;using System.Diagnostics;// Method exposed by a Data Access Layer (DAL) Componentpublic string GetProductName( int ProductID ){ SqlConnection conn = null; // Enclose all data access code within a try block try { conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind"); conn.Open(); SqlCommand cmd = new SqlCommand("LookupProductName", conn ); cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@ProductID", ProductID ); SqlParameter paramPN = cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 ); paramPN.Direction = ParameterDirection.Output; cmd.ExecuteNonQuery(); // The finally code is executed before the method returns return paramPN.Value.ToString(); } catch (SqlException sqlex) { // Handle data access exception condition // Log specific exception details LogException(sqlex); // Wrap the current exception in a more relevant // outer exception and re-throw the new exception throw new DALException( "Unknown ProductID: " + ProductID.ToString(), sqlex ); } catch (Exception ex) { // Handle generic exception condition . . . throw ex; } finally { if(conn != null) conn.Close(); // Ensures connection is closed }}// Helper routine that logs SqlException details to the // Application event logprivate void LogException( SqlException sqlex ){ EventLog el = new EventLog(); el.Source = "CustomAppLog"; string strMessage; strMessage = "Exception Number : " + sqlex.Number + "(" + sqlex.Message + ") has occurred"; el.WriteEntry( strMessage ); foreach (SqlError sqle in sqlex.Errors) { strMessage = "Message: " + sqle.Message + " Number: " + sqle.Number + " Procedure: " + sqle.Procedure + " Server: " + sqle.Server + " Source: " + sqle.Source + " State: " + sqle.State + " Severity: " + sqle.Class + " LineNumber: " + sqle.LineNumber; el.WriteEntry( strMessage ); }}在 SqlException catch 块内部,代码首先使用 LogException 辅助函数记录异常细节。该函数使用 foreach 语句来枚举 Errors 集合内部的特定于提供程序的细节,并将错误细节记录到错误日志中。catch 块中的代码随后将 SQL Server 特有的异常包装在 DALException 类型的异常内部,后者对于 GetProductName 方法的调用方更有意义。异常处理程序使用 throw 关键字将该异常传播回调用方。
更多信息
| • | 有关 SqlException 类的成员的完整列表,请参阅 http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemDataSqlClientSqlExceptionMembersTopic.asp。 |
| • | 有关开发自定义异常、记录和包装 .NET 异常以及使用各种方法来传播异常的详细指导,请参阅 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/exceptdotnet.asp。 |
从存储过程中产生错误
Transact-SQL (T-SQL) 提供了一个 RAISERROR函数(请注意拼写),可用来产生自定义错误并将其返回到客户端。对于 ADO.NET 客户端,SQL Server .NET 数据提供程序会截获这些数据库错误,并将其转换为 SqlError 对象。
使用 RAISERROR 函数的最简单方法是将消息正文包含为第一个参数,然后指定严重度和状态参数,如以下代码片段所示。
RAISERROR( 'Unknown Product ID: %s', 16, 1, @ProductID )
在该示例中,使用了一个替代参数将当前产品 ID 作为错误消息正文的一部分返回。第二个参数是消息严重度,第三个参数是消息状态。
更多信息
| • | 要避免对消息正文进行硬编码,可以使用 sp_addmessage 系统存储过程或使用 SQL Server 企业管理器,将您自己的消息添加到 sysmessages 表中。然后,您可以使用传递给 RAISERROR 函数的 ID 引用该消息。您定义的消息 ID 必须大于 50,000,如以下代码片段所示。 RAISERROR( 50001, 16, 1, @ProductID ) |
| • | 有关 RAISERROR 函数的详细信息,请在 SQL Server 联机图书索引中查找 RAISERROR。 |
适当地使用严重度级别
请认真选择错误严重度级别,并注意各个级别的影响。错误严重度级别的范围为 0 到 25,用于表示 SQL Server 2000 已经遇到的问题类型。在客户端代码中,可以通过检查 SqlError 对象的 Class 属性获得错误的严重度,该对象位于 SqlException 类的 Errors 集合中。表 1 指出了各种严重度级别的影响和含义。
表 1. 错误严重度级别 — 影响和含义
| 严重度级别 | 是否关闭连接 | 产生 SqlException | 含义 |
| 10 及更低 | 否 | 否 | 指示性消息,不一定代表错误状况。 |
| 11 - 16 | 否 | 是 | 可由用户纠正的错误,例如,通过修正的输入数据重试操作。 |
| 17 - 19 | 否 | 是 | 资源或系统错误。 |
| 20 - 25 | 是 | 是 | 致命系统错误(包括硬件错误)。客户端的连接被终止。 |
控制自动事务处理
对于所遇到的任何严重度超过 10 的错误,SQL Server .NET 数据提供程序都会引发 SqlException。当自动 (COM+) 事务处理中的某个组件检测到 SqlException 时,该组件必须确保它赞成中止该事务处理。这可能是也可能不是自动过程,具体取决于是否用 AutoComplete 属性对该方法进行了标记。
有关在自动事务处理的上下文中处理 SqlException 的详细信息,请参阅本文档中的确定事务处理结果一节。
检索指示性消息
严重度级别 10 及更低级别用于表示指示性消息,并且不会导致引发 SqlException。
检索指示性消息:
| • | 创建一个事件处理程序,并预订由 SqlConnection 对象公开的 InfoMessage 事件。该事件的委托如以下代码片段中所示。 public delegate void SqlInfoMessageEventHandler( object sender, SqlInfoMessageEventArgs e ); |
消息数据可通过传递给事件处理程序的 SqlInfoMessageEventArgs 对象获得。该对象公开了一个 Errors 属性,该属性包含一组 SqlError 对象 — 每条指示性消息对应一个对象。以下代码片段阐明了如何注册一个用于记录指示性消息的事件处理程序。
public string GetProductName( int ProductID ){ SqlConnection conn = null; try { conn = new SqlConnection( "server=(local);Integrated Security=SSPI;database=northwind"); // Register a message event handler conn.InfoMessage += new SqlInfoMessageEventHandler( MessageEventHandler ); conn.Open(); // Setup command object and execute it . . . } catch (SqlException sqlex) { // log and handle exception . . . } finally { if(conn != null) conn.Close(); }}// message event handlervoid MessageEventHandler( object sender, SqlInfoMessageEventArgs e ){ foreach( SqlError sqle in e.Errors ) { // Log SqlError properties . . . }}性能
本节介绍一些常见的数据访问方案,对于每种方案,都提供了就 ADO.NET 数据访问代码而言性能最高、可伸缩性最强的解决方案的有关详细信息。在适当的地方,对性能、功能和开发工作进行了比较。本节考虑了下列功能方案:
| • | 检索结果集并对检索到的行进行迭代处理。 |
| • | 检索具有指定主键的单个行。 |
| • | 从指定行检索单个项。 |
| • | 检查是否存在具有特定主键的行。这是单项查找方案的变种,此时返回一个简单的 Boolean 类型值就足够了。 |
检索多行
在该方案中,您希望检索一组表格形式的数据,并对检索到的行进行迭代以执行操作。例如,您可能希望检索一组数据,以不连续的方式处理这些数据,并将其作为 XML 文档传递给客户端应用程序(可能是通过 Web 服务传递)。或者,您还可能希望以 HTML 表的形式显示这些数据。
要帮助确定最适当的数据访问方法,请考虑您是需要(不连续的)DataSet 对象所提供的额外的灵活性,还是需要 SqlDataReader 对象(它非常适合于企业对用户 (B2C) Web 应用程序中的数据展示)所提供的原始性能。图 4 显示了这两种基本方案。
注 用于在内部填充 DataSet 的 SqlDataAdapter 使用 SqlDataReader 来访问数据。

图 1.4.多行数据访问方案
比较几种选择
当从数据源中检索多个行时,您具有下列选择:
| • | 使用 SqlDataAdapter 对象生成 DataSet 或 DataTable。 |
| • | 使用 SqlDataReader 提供只读的、只进的数据流。 |
| • | 使用 XmlReader 提供 XML 数据的只读、只进数据流。 |
选择 SqlDataReader 还是 DataSet/DataTable,从根本上说是一个注重性能还是注重功能的问题。SqlDataReader 可提供最佳性能;DataSet 可提供额外的功能和灵活性。
数据绑定
上述所有三个对象都可以充当数据绑定控件的数据源,尽管 DataSet 和 DataTable 可以充当比 SqlDataReader 更多种控件的数据源。这是因为 DataSet 和 DataTable 实现了 IListSource(产生 IList),而 SqlDataReader 则实现了 IEnumerable。一些支持数据绑定的 WinForm 控件需要实现了 IList 的数据源。
这一差异的原因在于为其设计各种对象类型的方案的类型。DataSet(它包含 DataTable)是一种丰富的、不连续的结构,同时适用于 Web 和桌面 (WinForm) 这两种方案。另一方面,数据读取器对需要执行优化的只进数据访问的 Web 应用程序进行了优化。
请检查您希望绑定到的特定控件类型的数据源要求。
在应用程序层之间传递数据
DataSet 为可以根据需要作为 XML 进行操作的数据提供了关系视图,并且使您可以在应用程序层和组件之间传递不连续的缓存数据副本。然而,SqlDataReader 提供了最佳性能,因为它避免了与 DataSet 的创建关联的性能和内存开销。请记住,DataSet 对象的创建可导致多个子对象(包括 DataTable、DataRow 和 DataColumn 对象)以及用作这些子对象的容器的集合对象的创建。
使用数据集
在下列情况下,请使用由 SqlDataAdapter 对象填充的 DataSet:
| • | 您需要不连续的内存驻留型数据缓存,以便可以将其传递给应用程序内的其他组件或层。 |
| • | 您需要数据在内存中的关系视图,以便进行 XML 或非 XML 操作。 |
| • | 您要处理从多个数据源(如多个数据库、表或文件)中检索到的数据。 |
| • | 您希望更新检索到的全部或部分行,并且使用 SqlDataAdapter 的批量更新功能。 |
| • | 您希望对其执行数据绑定的控件需要支持 IList 的数据源。 注 有关详细信息,请参阅 MSDN 网站上的“Designing Data Tier Components and Passing Data Through Tiers”,网址为 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/BOAGag.asp。 |
更多信息
如果您使用 SqlDataAdapter 来生成 DataSet 或 DataTable,请注意下列事项:
| • | 不需要显式打开或关闭数据库连接。SqlDataAdapterFill 方法可打开数据库连接,然后在其返回之前关闭该连接。如果连接已经打开,Fill 将使该连接保持打开。 |
| • | 如果您需要将该连接用于其他目的,请考虑在调用 Fill 方法之前打开它。这样,您可以避免不必要的打开/关闭操作,从而提高性能。 |
| • | 尽管您可以反复使用同一 SqlCommand 对象来多次执行同一命令,请不要重用同一 SqlCommand 对象来执行不同的命令。 |
| • | 有关说明如何使用 SqlDataAdapter 来填充 DataSet 或 DataTable 的代码示例,请参阅附录中的如何使用 SqlDataAdapter 来检索多个行。 |
使用 SqlDataReader
在下列情况下,请使用通过调用 SqlCommand 对象的 ExecuteReader 方法得到的 SqlDataReader:
| • | 您要处理大量的数据 — 多得难以在单个缓存中进行维护。 |
| • | 您希望减少应用程序的内存使用量。 |
| • | 您希望避免与 DataSet 相关联的对象创建开销。 |
| • | 您希望用对实现了 IEnumerable 的数据源提供支持的控件执行数据绑定。 |
| • | 您希望简化和优化数据访问。 |
| • | 您要读取的行包含二进制大对象 (BLOB) 列。您可以使用 SqlDataReader 在可管理的块区中将 BLOB 数据从数据库中提取出来,而不是一次将其全部提取出来。有关处理 BLOB 数据的详细信息,请参阅本文档中的处理 BLOB 一节。 |
更多信息
如果您使用 SqlDataReader,请注意以下事项:
| • | 当数据读取器处于活动状态时,到数据库的基础连接将保持打开状态,并且无法用于任何其他目的。尽可能早地调用 SqlDataReader 上的 Close。 |
| • | 每个连接只能有一个数据读取器。 |
| • | 您可以在使用完数据读取器后显式关闭连接,或者通过将 CommandBehavior.CloseConnection 枚举值传递给 ExecuteReader 方法,将连接的生存期与 SqlDataReader 对象联系起来。这表示在关闭 SqlDataReader 后,应该关闭连接。 |
| • | 在使用读取器访问数据时,如果知道列的基础数据类型,应使用类型化的访问器方法(如 GetInt32 和 GetString),这是因为它们可减少读取列数据时需要执行的类型转换的数量。 |
| • | 要避免将不需要的数据从服务器提取到客户端,如果您希望关闭读取器并丢弃其余任何结果,应在调用读取器上的 Close 之前,调用命令对象的 Cancel 方法。Cancel 确保在服务器上丢弃结果,而不会将结果无谓地提取到客户端上。否则,调用数据读取器上的 Close 会导致读取器无谓地提取其余结果以清空数据流。 |
| • | 如果您希望获得输出或者从存储过程返回的返回值,并且您使用的是 SqlCommand 对象的 ExecuteReader 方法,则必须在输出和返回值可用之前调用读取器上的 Close 方法。 |
| • | 有关说明如何使用 SqlDataReader 的代码示例,请参阅附录中的如何使用 SqlDataReader 来检索多个行。 |
使用 XmlReader
在以下情况下,请使用通过调用 SqlCommand 对象的 ExecuteXmlReader 方法得到的 XmlReader:
| • | 您希望将检索到的数据作为 XML 处理,但您不希望创建 DataSet 带来性能开销,并且不需要不连续的数据缓存。 |
| • | 您希望利用 SQL Server 2000 FOR XML 子句的功能,该子句可用来以灵活的方式从数据库中检索 XML 片段(即不带根元素的 XML 文档)。例如,使用该方法可以指定准确的元素名称,还可以指定是否应该使用以元素或属性为中心的架构,是否应该通过 XML 数据返回架构,等等。 |
更多信息
如果您使用 XmlReader,请注意以下事项:
| • | 在您从 XmlReader 中读取数据时,连接必须保持打开。SqlCommand 对象的 ExecuteXmlReader 方法目前不支持 CommandBehavior.CloseConnection 枚举值,因此您在使用完读取器后必须显式关闭连接。 |
| • | 有关说明如何使用 XmlReader 的代码示例,请参阅附录中的如何使用 XmlReader 来检索多个行。 |
检索单个行
在该方案中,您希望从数据源中检索包含指定列集的单行数据。例如,您有一个客户 ID 并希望查找相关的客户详细信息,或者您有一个产品 ID 并希望检索产品信息。
比较几种选择
如果您希望使用从数据源中检索到的单行执行数据绑定,可以使用 SqlDataAdapter,以与前面讨论的“多行检索和迭代”方案中介绍的相同方式来填充 DataSet 或 DataTable。然而,除非您特别需要 DataSet/DataTable 功能,否则应该避免创建这些对象。
如果您需要检索单个行,请使用下列选择之一:
| • | 使用存储过程输出参数。 |
| • | 使用 SqlDataReader 对象。 |
这两个选择都可以避免由于在服务器上创建结果集和在客户端上创建 DataSet 而带来的不必要的开销。各个方法的相对性能取决于压力级别以及是否启用了数据库连接池。当启用了数据库连接池时,性能测试表明,存储过程方法在高压力条件(同时存在 200 个以上的连接)下的性能比 SqlDataReader 方法高将近 30%。
使用存储过程输出参数
当您希望从已经启用连接池的多层 Web 应用程序中检索单个行时,请使用存储过程输出参数。
更多信息
有关说明如何使用存储过程输出参数的代码示例,请参阅附录中的如何使用存储过程输出参数来检索单个行。
使用 SqlDataReader
在以下情况下,请使用 SqlDataReader:
| • | 除了数据值以外,您还需要元数据。您可以使用数据读取器的 GetSchemaTable 方法获得列元数据。 |
| • | 您未使用连接池。如果禁用了连接池,SqlDataReader 在所有压力条件下都是一个不错的选择;性能测试已经表明,在存在 200 个浏览器连接的条件下,它的性能比存储过程方法高 20% 左右。 |
更多信息
如果您使用 SqlDataReader,请注意以下事项:
| • | 如果您知道查询仅返回单个行,请在调用 SqlCommand 对象的 ExecuteReader 方法时使用 CommandBehavior.SingleRow 枚举值。一些提供程序(如 OLE DB .NET 数据提供程序)使用这一提示来优化性能。例如,该提供程序通过使用 IRow 接口(如果该接口可用)而不是开销更大的 IRowset 来执行绑定。该方法对 SQL Server .NET 数据提供程序没有影响。 |
| • | 如果您的 SQL Server 命令包含输出参数或返回值,在关闭 DataReader 之前它们将不可用。 |
| • | 在使用 SqlDataReader 对象时,请始终通过 SqlDataReader 对象的类型化访问器方法(例如 GetString 和 GetDecimal)来检索输出参数。这可避免不必要的类型转换。 |
| • | .NET 框架 1.1 版包含一个名为 HasRows 的附加 DataReader 属性,使用该属性,可以在从 DataReader 中读取之前确定它是否已经返回结果。 |
| • | 有关说明如何使用 SqlDataReader 对象检索单个行的代码示例,请参阅附录中的如何使用 SqlDataReader 来检索单个行。 |
检索单个项
在该方案中,您希望检索单个数据项。例如,您可能希望查找单个产品名(给定了产品 ID)或单个客户信用等级(给定了客户名称)。在此类方案中,您通常不希望在检索单个项时产生 DataSet 的开销,甚至不希望产生 DataTable 的开销。
您可能还希望简单地检查一下数据库中是否存在特定行。例如,当新用户在网站上注册时,您需要检查选择的用户名是否已存在。这是单项查找的特殊情况,但在此情况下,简单的 Boolean 类型返回值就足够了。
比较几种选择
当您从数据源中检索单个数据项时,请考虑下列选择:
| • | 通过存储过程使用 SqlCommand 对象的 ExecuteScalar 方法。 |
| • | 使用存储过程输出或返回参数。 |
| • | 使用 SqlDataReader 对象。 |
ExecuteScalar 方法直接返回数据项,因为它专用于只返回单个值的查询。它所需要的代码比存储过程输出参数和 SqlDataReader 方法需要的代码都要少。
从性能角度而言,应该使用存储过程输出或返回参数,因为测试表明,存储过程方法在低压力条件和高压力条件下(从同时存在的浏览器连接少于 100 个到同时存在 200 个浏览器连接)都能够提供一致的性能。
更多信息
在检索单个项时,请注意以下事项:
| • | 如果查询通常返回多个列和/或行,则通过 ExecuteQuery 执行该查询将仅返回第一行的第一列。 |
| • | 有关说明如何使用 ExecuteScalar 的代码示例,请参阅附录中的如何使用 ExecuteScalar 来检索单个项。 |
| • | 有关说明如何使用存储过程输出或返回参数来检索单个项的代码示例,请参阅附录中的如何使用存储过程输出或返回参数来检索单个项。 |
| • | 有关说明如何使用 SqlDataReader 对象检索单个项的代码示例,请参阅附录中的如何使用 SqlDataReader 来检索单个项。 |
通过防火墙进行连接
您将经常需要配置 Internet 应用程序以通过防火墙连接到 SQL Server。例如,许多 Web 应用程序及其防火墙的关键体系结构组件是外围网络(也称为 DMZ,即非管制区),它用于将前端 Web 服务器与内部网络隔离。
通过防火墙连接到 SQL Server 时,要求对防火墙、客户端和服务器进行特殊的配置。SQL Server 提供了客户端网络实用工具和服务器网络实用工具程序来帮助进行配置。
选择网络库
在通过防火墙进行连接时,可使用 SQL Server TCP/IP 网络库来简化配置。这是 SQL Server 2000 安装的默认配置。如果您使用的是较低版本的 SQL Server,请确保已经分别通过使用客户端网络实用工具和服务器网络实用工具,在客户端和服务器上将 TCP/IP 配置为默认网络库。
除了配置方面的好处以外,使用 TCP/IP 库结果还意味着您可以:
| • | 由于在处理大量数据时的性能改善以及可伸缩性得到提高而受益。 |
| • | 避免其他与命名管道关联的安全问题。 |
您必须将客户端计算机和服务器计算机配置为使用 TCP/IP。因为大多数防火墙都对允许通信的端口集进行限制,所以您还必须对 SQL Server 使用的端口号给予认真的考虑。
配置服务器
SQL Server 的默认实例在端口 1433 上侦听。同时,还使用了 UDP 端口 1434,以使 SQL 客户端能够定位其网络中的其他 SQL 服务器。然而,SQL?Server?2000 的命名实例在首次启动时动态分配端口号。网络管理员不希望在防火墙上打开一系列端口号;因此,在使用带防火墙的 SQL Server 命名实例时,请使用服务器网络实用工具来配置该实例,以使其在特定端口号上侦听。然后,您的管理员可以配置防火墙,以允许通信到达该服务器实例正在侦听的特定 IP 地址和端口号。
注 客户端网络库使用的源端口是在范围 1024 - 5000 中动态分配的。对于 TCP/IP 客户端应用程序,这是标准做法,但这意味着您的防火墙必须允许来自该范围内任意端口的通信。有关 SQL Server 使用的端口的详细信息,请参阅 Microsoft 知识库文章 287932“INF:TCP Ports Needed for Communication to SQL Server Through a Firewall”。
动态发现命名实例
如果您更改 SQL Server 所侦听的默认端口号,请配置客户端以连接到此端口。有关详细信息,请参阅本文档中的配置客户端一节。
在为 SQL Server 2000 的默认实例更改端口号后,如果未能修改客户端,将导致连接错误。如果有多个 SQL Server 实例,最新版本的 MDAC 数据访问栈 (2.6) 将使用动态发现,并且使用用户数据文报协议 (UDP) 协商(通过 UDP 端口 1434)来查找命名实例。尽管这可能在开发环境中有效,但却不太可能在实际应用环境中起作用,因为防火墙通常会阻塞 UDP 协商通信。
要避免这一问题,请始终配置客户端以连接到已配置的目标端口号。
配置客户端
应该配置客户端以使用 TCP/IP 网络库连接到 SQL?Server,并且应确保客户端库使用正确的目标端口号。
使用 TCP/IP 网络库
您可以使用 SQL Server 客户端网络实用工具来配置客户端。在某些安装中,客户端(例如,您的 Web 服务器)上可能尚未安装该实用工具。在此情况下,您可以执行下列操作之一:
| • | 通过使用由连接字符串提供的名称-值对“Network Library=dbmssocn”指定网络库。字符串 "dbmssocn" 用于标识 TCP/IP(套接字)库。 注 在使用 SQL Server .NET 数据提供程序时,网络库设置在默认情况下使用 "dbmssocn"。 |
| • | 修改客户端计算机上的注册表,以便将 TCP/IP 设置为默认库。有关配置 SQL Server 网络库的详细信息,请参阅 HOWTO:Change SQL Server Default Network Library Without Using Using Client Network Utility (Q250550)。 |
指定端口
如果您的 SQL Server 实例被配置为在除默认的 1433 以外的端口上侦听,可以通过以下方法指定要连接到端口号:
| • | 使用客户端网络实用工具。 |
| • | 指定端口号,并将“Server”或“Data Source”名称-值对提供给连接字符串。使用具有以下格式的字符串: "Data Source=ServerName, PortNumber" 注ServerName 可能是 IP 地址或域名系统 (DNS) 名称。要获得最佳性能,请使用 IP 地址以避免执行 DNS 查找。 |
分布式事务处理
如果您已经开发了使用 COM+ 分布式事务处理以及 Microsoft 分布式事务处理协调器 (DTC) 的服务的服务组件,您可能还需要配置您的防火墙以允许 DTC 通信在独立的 DTC 实例之间流动,以及在 DTC 和资源管理器(如 SQL Server)之间流动。
有关为 DTC 打开端口的详细信息,请参阅 INFO:Configuring Microsoft Distributed Transaction Coordinator (DTC) to Work Through a Firewall。
处理 BLOB
现在,许多应用程序除了处理较为传统的字符和数值数据以外,还需要处理诸如图形和声音之类的数据格式,甚至需要处理更为复杂的数据格式,如视频。存在许多不同类型的图形、声音和视频格式。不过,从存储角度而言,它们都可以视为二进制数据块,通常称为二进制大对象,即 BLOB。
SQL Server 提供了 binary、varbinary 和 image 数据类型来存储 BLOB。尽管具有现在的名称,BLOB 数据还可以指基于文本的数据。例如,您可能希望存储可以与特定行关联的任意长的注释字段。为此,SQL Server 提供了 ntext 和 text 数据类型。
通常,对于小于 8 KB 的二进制数据,请使用 varbinary 数据类型。对于超过此大小的二进制数据,请使用 image。表 2 介绍了每个数据类型的主要功能。
表 2. 数据类型功能
| 数据类型 | 大小 | 说明 |
| binary | 从 1 到 8,000 字节。存储大小为指定长度加 4 字节。 | 固定长度二进制数据 |
| varbinary |