编辑1 - 数据访问应用程序块介绍
Enterprise Library 数据访问应用程序块简化了实现常规数据访问功能的开发任务。应用程序可以在各种场景中使用此应用程序块,例如为显示而读取数据、传递数据穿过应用程序层( application layers)、以及将修改的数据提交回数据库系统。应用程序块包含对存储过程和内联 SQL 的支持。常规内部(housekeep)处理,如管理连接、创建并缓存参数,都封装在应用程序块的方法中。换句话说,数据访问应用程序块在简单易用的类中提供了对 ADO.NET 的最常用的特性的访问;这提高了开发人员的工作效率。
ADO.NET 2.0 提供了如
DbCommand 类和
DbConnection 这样的类,这些类有助于从任何特定数据库实现中抽象出数据提供程序。数据访问应用程序块利用了这些类,并且提供了加强支持数据库特定特性封装的模型,例如参数发现和类型转换。因此,应用程序可以在不修改客户代码的情况下从一个数据库移植到另一个数据库。数据访问应用程序块包括一个抽象基类,它定义了一个通用的接口,并提供了许多在 ADO.NET 2.0 中可用的数据访问方法所需要的实现。
应用程序块还包含了专用于 Microsfot SQL Server、Microsoft SQL Server CE、和 Oracel 的类。这些类完成对特定数据库类型的操作。应用程序的代码只为一种数据库而编写,例如 SQL Server,可以看到有许多为另一种数据库编写的代码是一样的,例如 Oracle 。
数据访问应用程序块的另一个特性是,应用程序代码可以由一个 ADO.NET 连接字符串的名字,如"Customer" 或者 "Inventory" ,而引向一个特定的数据。应用程序代码可以指定一个数据库命名实例,并传递此参数到
DatabaseFactory.CreateDatabase 方法。每个命名数据库都有连接字符串保存在配置文件中。通过修改配置文件中的设置,开发人员可以在不同的数据库配置下使用应用程序而不需要重新编译代码。
数据访问应用程序块提供了下列好处:
- 使用了由 ADO.NET 2.0 提供的功能并与其一起使用,可以同时使用 ADO.NET 和应用程序块的功能。
- 减少编写重复代码完成标准任务的需要。
- 有助于维护一致的数据访问实践,无论是在应用程序内部还是企业间。
- 减少了变更数据库类型的困难。
- 将开发人员从学习用于不同数据库的不同编程模型中解放出来。
- 减少了在开发人员移植应用程序到另一种数据库时不得不编写的代码的数量。
普通场景开发人员经常编写使用数据库的应用程序。因为它太普遍了,开发人员可能会发现他们为每个应用程序在重复编写同样的代码。另外,这些应用程序可能需要与不同的数据库一起工作。尽管任务是相同的,代码也必须适配以适应每个数据库的编程模型。数据访问应用程序块通过提供完成最常用的数据访问任务的逻辑来解决这些问题。开发人员仅需要做如下事情:
- 创建一个 database 对象。
- 提供用于命令的参数,如果需要的话。
- 调用适当的方法,这些方法已经过性能优化,并且是可移植的。
数据访问应用程序块可以透明的与 SQL Server、SQL Server CE、和 Oracle 数据库一起工作。
示例应用程序代码下列代码展示了如何调用一个存储过程并返回一个
DataSet。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory");
// Retrieve products from category 7.
db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, 7);
DataSet productDataSet = db.ExecuteDataSet(dbCommand);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand = db.GetStoredProcCommand("GetProductsByCategory")
' Retrieve products from the category 7.
db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, 7)
Dim productDataSet As DataSet = = db.ExecuteDataSet(dbCommand)
读者需求此手册的目标读者是软件架构师和软件开发人员。要从此手册中获得最大好处,将需要理解下列的技术:
- Microsoft Visual Studio 2005 开发系统 (下列任何版本):
- Visual Studio 2005 专业版
- Visual Studio 2005 团队系统版
- Visual Studio 2005 标准版
- .NET Framework version 2.0
- Microsoft SQL Server, Microsoft SQL Server CE 或者 Oracle 数据库
迁移到 Enterprise Library 3.1 — May 2007一般情况下,使用数据访问应用程序块的 January 2006 发布构建的应用程序不需要修改任何代码就能使用 May 2007 发行的功能。可能需要更新引用以指向新的程序集,并更新配置文件以引用正确的应用程序版本。
已修改某些 Database 类的方法以使用 .NET Framework 2.0 的
TransactionScope 类。这些方法,例如
ExecuteNonQuery ,已通过用
GetOpenConnection 方法替换掉
GetConnection 方法来修改为识别
TransactionScop 实例的有效时机。如果编写了一个继承自
Database 类的类,将需要考虑这些变化来重写代码。如果继续使用
GetConnection 方法,将会收到一个编译错误。
另外,如果应用程序使用了
ExecuteXmlReader 方法,可能需要重写代码以测试查看在关闭连接前
TransactionScope 实例是否是有效的。
更多信息,请参见使用 TransactionScope 类和如何使用
ExecuteXMLReader 方法的 XML 示例的获取多行数据。
新的特性Enterprise Library 3.1 – May 2007 支持 SQL Server Compact Edition (CE)。SQL Server CE 提供了精减的关系数据库的特性,以用于桌面和移动应用程序,这些程序需要本地数据库存储但又不需要完整的 SQL Server 的功能。更多信息,请参见使用 SQL Server CE 。
Database.UpdateDataSet 方法有一个新的使用
updateBatchSize 参数的重载。设置
updateBatchSize 参数为一个正整数值将使
DataAdapter 对象以指定大小为批次发送更新到数据库。这将减少数据库往返的次数。
系统需求要运行数据访问应用程序块,你需要下列系统:
- Microsoft Windows XP Professional, Windows Server 2003, 或者 Windows Vista 操作系统
- Microsoft .NET Framework 2.0
- Microsoft Visual Studio 2005 开发系统 (任何下列版本):
- Microsoft Visual Studio 2005 Standard Edition
- Microsoft Visual Studio 2005 Professional Edition
- Microsoft Visual Studio 2005 Team Edition for Software Developers
- Microsoft Visual Studio 2005 Team Edition for Software Testers
- Microsoft Visual Studio 2005 Team Edition for Software Architects
- Microsoft Visual Studio 2005 Team Suite
- 运行受 .NET Framework 2.0 数据提供程序支持数据库的数据库服务器。包含 SQL Server 2000或更新版本,SQL Server CE 和 Oracle 9i 或更新版本。运行服务器也可以运行受用于 OLE DB 或 ODBC 的 .NET Framework 2.0 数据提供程序支持的数据库。
数据访问应用程序块依赖数据访问应用程序块依赖于 Enterprise Library 中的其他代码:
- 内核库功能。Enterprise Library 内核提供了服务,如度量和配置,是一个所有 Enterprise Library 应用程序块的共享依赖。内核库功能包含在程序集 Microsoft.Practices.EnterpriseLibrary.Common.dll 中。
- ObjectBuilder 子系统。ObjectBuilder 子系统完成所有重复且必要的和销毁对象实例的任务,同时提供了高度的灵活性。Enterprise Library 将 ObjectBuilder 子系统用于如注入配置到程序块的类中以及连接度量类到应用程序块这样的任务。ObjectBuilder 子系统包含在程序集 Microsoft.Practices.ObjectBuilder.dll 中。
数据访问应用程序块使用 ADO.NET 来进行连接字符串的管理。应用程序块提供了使用配置信息创建 database 对象的
DatabaseFactory 类。配置信息由 ADO.NET 管理的连接字符串和其他和程序块配置信息。
注意:默认情况下,应用程序块的配置信息源是应用程序的配置文件。使用 Enterprise Library ,可以修改配置信息源。例如,可以选择将应用程序块的配置设置保存到 SQL 数据库中。Database 工厂类首先尝试从默认配置源中读取连接字符串信息。如果没有,Database 工厂类再尝试从应用程序配置文件中的 ADO.NET 管理配置节中读取连接字符串信息。
通常,使用配置数据来管理连接字符串。另一种就是,可以使用构建函数,并提供给它连接字符串和数据库类型来在应用程序中创建一个数据库对象。
修改数据访问应用程序块配置信息的推荐方法是使用 Enterprise Library 配置控制台。
数据访问应用程序块的文档除了简介以外,文档还包括了下列部分:
- 使用数据访问应用程序块开发应用程序。此节被分割成了几个子章节。输入配置信息描述了如何配置应用程序块。添加应用程序代码描述了如何为使用数据访问应用程序块来准备应用程序。下一小节,关键场景,示例了如何使用应用程序块完成绝大多数典型的数据访问操作。最后一小节,开发任务细节,包括关于创建 database 对象、创建 command 、处理参数和处理异常的深度信息。
- 数据访问应用程序块的设计。此节描述了设计应用程序块及相关的决定。
- 扩展和修改数据访问应用程序块。此节描述了如何通过添加自己的数据库提供程序来扩展应用程序及给出了修改源代码的建议。
- 部署和操作。此节描述子如何部署和更新应用程序块程序集,还包括关于配置和 SQL Server 安全的信息。
- 快速入门。此节描述了如何安装和配置快速入门应用程序,还包括了一系列的示范了如何添加普通数据访问操作到应用程序中的漫游。
更多信息更多信息,请参见下列资源:
顶部编辑1.1 - 场景和目标
数据访问应用程序块为解决开发人员在编写数据库应用程序时所面对的绝大多数普通任务而设计。这些任务根据场景进行了组织。每个场景都人出了一个真实世界条件下的示例,例如从分类中获取信息或者未完成银行事务,描述了条件所要求的数据库功能,并展示完成任务的代码。
根据场景组织这些任务的目的是给代码一些上下文,来替代展示一组孤立的方法,而没有它们最好使用在哪儿的意义,场景为代码提供了一种设置,将它放置在其应用程序必须访问数据库的许多开发人员所熟悉的条件中。
场景如下:
- 使用 DataReader 获取多行数据
- 使用 DataSet 获取多行数据
- 运行一个命令并获取输出参数
- 运行一个命令并获取单值项
- 在一个事务中执行多个操作
- 从 SQL Server 中获取 XML 数据
- 使用包含在 DataSet 对象中的数据更新数据库
何时使用数据访问应用程序块数据访问应用程序块包含少量简化绝大多数访问数据库的普通方法的方法。每个方法都封装了获取数据所需要的逻辑以及管理数据库连接。如果应用程序中使用标准的数据访问技术就可以考虑使用应用程序块。
应用程序块补充了 ADO.NET 2.0 中的代码,以让你在不同的数据库类型中使用同样的代码。它包含了用于 SQL Server 和 Oracle 数据库的类。这些类包含了提供特定数据库特性如参数处理和游标的实现的代码。另外,
GenericDatabase 类允许使用应用程序块与任何配置的 ADO.NET 2.0
DbProviderFactory 对象一起使用。可以通过添加新的惟数据库特定特性或者提供已有数据库自定义实现的数据库类型来扩展应用程序块。仅仅需要在在一个用于目标数据库的 ADO.NET 2.0
DbProviderFactory 类。
何时直接使用 ADO.NET数据访问应用程序块是 ADO.NET 的一个补充;而不是替换。应用程序块提供了简化和方便,同时帮助开发人员以最佳实践使用 ADO.NET 。如果应用程序需要以特殊的方法获取数据,或者代码需要定制以利用特定于特定数据库的特性,使用 ADO.NET 可能更适合。
顶部编辑2 - 用数据访问应用程序块开发应用程序
此主题描述了如何使用数据访问应用程序块开发应用程序。首先解释了如何配置应用程序块并将它添加到应用程序中。然后,在关键场景中,解释了如何在特定场景中使用应用程序块,例如获取单个项或者使用
DataSet 对象获取多行。最后,在开发任何细节中,给出了关于如连接管理、参数处理和处理异常等方面的更多信息。本主题假设使用的是原始的应用程序块,即没有扩展的。(要学习如何添加功能,请参见扩展和修改数据访问应用程序块。)
所有应用程序块都是源代码形式发布的,在使用快速入门和配置控制台之前必须进行编译。要学习如何编译 Enterprise Library 源代码,请参见构建 Enterprise Library 。
顶部
编辑2.1 - 输入配置信息
下面这些过程展示了如何配置数据访问应用程序块。
此过程解释了如何配置数据访问应用程序块。与节点关联的属性显示在右边的面板里。
添加数据访问应用程序块- 打开配置文件。更多信息,请参数配置应用程序块。
- 右单击Application Configuration,指向 New ,然后单击 Data Access Application Block。
下一过程解释了如何配置默认的数据库实例,此实例在应用程序调用不指定实例名称的
DatabaseFactory.CreateDatabase 方法时使用。
配置默认数据库- 在右面板中,展开 DefaultDatabase 属性。
- 为 DefaultDatabase 属性输入连接字符串的名称或者从下拉列表中选择它。默认的连接字符串名称是 ConnectionString 。
- (可选的)输入一个新的名称来设计 Name 属性,默认名称是 ConnectionString。
- 在 ProviderName 属性节,如果愿意,可能修改提供程序的名称。输入提供程序的名称或者从下拉列表中选择它。提供程序的默认名称是 System.Data.SqlClient 。ProviderName 属性必须是一个在 DBProviderFactory 类中指定的提供程序的名称。
下一过程解释了如何为命名数据库实例创建连接字符串。当配置控制台保存连接字符串时,它生成了一个以 name = value 格式保存值对的以分号分割的字符串。例如,如果使用配置控制台来用默认值生成连接字符串,配置控制台台将生成下列连接字符串。
Database=Database;Server=(local)\SQLEXPRESS;Integrated Security=SSPI;
配置连接字符串- 单击 ConnectionString 节点。
- (可选的)输入一个新的名称以设置 Name 属性,这是 ConnectionString 节点的名称。默认的名称是 ConnectionString 。
- (可选的)在 ProviderName 属性节,修改提供程序的名称。输入提供程序的名称或者从下拉列表中选择它。默认的提供程序的名称是 System.Data.SqlClient 。ProviderName 属性必须是一个指定在 DbProviderFactory 类中的提供程序的名称。
- 用下列值更新 ConnectionString 属性。
Database=Database;Server=(local)\SQLEXPRESS;Integrated Security=SSPI
下一过程展示了如何配置一个 SQL Server CE 数据库。如果应用程序总是使用在配置期间命名的单一文件,这些步骤是合适的。关于 SQL Server CE 的更多信息,请参见创建数据库对象的细节。
配置 SQL Server CE- 右单击 Custom Provider Mappings,指向 New ,然后单击 Provider Mapping 。
- 在属性面板中单击 TypeName 属性。单击省略号(...)按钮。
- 在 Type Selector 中,找到并双击 SqlCeDatabase 。
- 在 ConnectionStrings 节点上右单击并单击 New ,然后单击 Connection String 。
- (可选的)输入新的名称以设置 Name 属性。这是 ConnectionString 节点的名称。默认的名称是 ConnectionString 。
- 在 ProviderName 属性节,修改提供程序的名称为 Microsoft.SqlServerCe.Client 。
- 在 ConnectionString 属性节,输入理想的 SQL Server CE 连接字符串,例如:
Data Source='C:\MyApp\MyDatabase.sdf'
下一过程展示了如果添加 Oracle 包。Oracel 包服务是分组存储过程到普通组的一种方式,通常基于它们的功能。当应用程序调用在包中的 Oracle 存储过程时,代码必须用包名做为存储过程的前缀。例如,要调用在命名为
Employee_pkg 的包中的名为
GetEmployeeName 的过程,将调用
Employee_pkg.GetEmployeeName 。
将这段代码加入到应用程序中将降低可移植性,因为语法专用于 Oracle 。另一种替换做法是,数据访问应用程序块会用包名做为存储过程的前缀。这意味着客户端代码在调用存储过程时不需要指定包名。要做到这一点,应用程序块使用在配置文件中的信息。OraclePackage 节点保存了一个名称/前缀对。名称是包的名称,前缀是一个与包相关的字符串。所有以指定的前缀开始的存储过程都假定在相关的包内。
当应用程序调用一个存储过程时,数据访问应用程序块检查看是否以配置文件中的某个前缀开始。如果是,应用程序块为存储过程加上相应的包名前缀。(应用程序块将使用找到的第一个匹配)。如果指定一个星号(*)为前缀,关联包将用于所有存储过程。
配置 Oracle 包- 右单击 ConnectionString ,指向 New ,然后单击 OraclePackages 。
- 单击 OraclePackage 。
- 输入 Oracle 包的名称以修改 Name 属性。默认为 OraclePackage 。
- 输入 Prefix 属性的值。
下一过程解释了如何通过关联提供程序和数据库全名称来添加自定义的提供程序映射。
配置自定义的提供程序- 右单击 CustomProviderMappings 节点,指向 New ,然后单击 ProviderMapping 。
- (可选的)输入新的名称以设置 Name 属性。输入提供程序的名称或从下拉列表中选择。默认的提供程序是 System.Data.SqlClient 。ProviderName 属性必须是一个在 DbProviderFactory 类中指定的提供程序名称。
- 在 TypeName 属性节中。单击省略号按钮(...)并使用'Type Selector 选择 Enterprise Library 数据库类型的全名称。
使用提示在这有二点关于配置文件的要记住。
- 在使用配置控制台打开已存在的包含数据访问应用程序块的应用程序配置文件时,它会显示存储于 Machine.config 文件中的连接字符串。虽然配置控制台的 GUI 接口允许编辑连接串,配置控制台无法修改 Machine.config 文件,而且所做的修改也不会起作用。配置控制台仅保存存储在应用程序配置文件中的连接字符串的修改。
- 配置文件是不加密的。配置文件可能包含关于连接字符串、用户标识、密码、数据库服务器和分类的敏感信息。可以使用加密技术来保护这些信息拒绝未授权的读写操作。关于加密配置文件的信息,请参见配置应用程序块。
顶部
编辑2.1.1 - 数据访问应用程序块的源模式
本主题列出了用于配置数据访问应用程序块的 XML 元素和属性。可以手工编辑 XML 数据,但 Enterprise Library 极大的简化了此任务。如果选择手工编辑 XML ,则要使用包含在本主题中的模式信息。
配置文件有如下的节处理程序声明:
<configSections>
<section name="dataConfiguration"
type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings,
Microsoft.Practices.EnterpriseLibrary.Data, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null" />
<section name="oracleConnectionSettings"
type="Microsoft.Practices.EnterpriseLibrary.Data.Oracle.Configuration.OracleConnectionSettings,
Microsoft.Practices.EnterpriseLibrary.Data, Version=3.1.0.0, Culture=neutral, PublicKeyToken=null" />
</configSections>
节处理程序声明包括配置设置节的名称和处理节中配置数据的节处理程序的类名。第一个配置设计节的名称为 dataConfiguration ,节处理程序的类名为
DatabaseSettings (在
Microsoft.Practices.EnterpriseLibrary.Data.Configuration 命名空间中)。
第二个配置设置节是 oracleConnectionSettings 。节处理程序类的名称为
OracleConnectionSettings (在
Microsoft.Practices.EnterpriseLibrary.Data.Oracle.Configuration 命名空间中 )。
connectionStrings 元素connectionStrings 元素列出了可被应用程序使用的数据库连接,此元素不是必须的。
属性和子元素下面的节描述了
connectionStrings 元素的属性和子元素。
add 子元素add 元素是
connectionStrings 元素的子元素。
add 元素添加一个数据库连接,此元素不是必须的,可以有多个
add 元素。
属性表 1 列出了
add 元素的属性。
表 1 :add 的属性| 属性 | 描述 |
|---|
| name | 由应用程序访问的数据库实例的逻辑名称。在节中,名称必须是唯一的。此属性是必须的。 |
| providerName | 提供程序的名称。默认情况下,提供程序的名称定义在 Machine.config 文件中。providerName 名称必须是一个在 DBProviderFactory 类中指定的提供程序的名称。此属性是必须的。 |
| connectionString | 可用于被选的提供程序的连接字符串,此属性是必须的。 |
dataConfiguration 元素元素仅在需要指定一个默认数据库或者自定义的提供程序的映射时有用。
属性表 2 列出了
dataConfiguration 元素的属性。
表 2 :dataConfiguration 的属性| 属性 | 描述 |
|---|
| defaultDatabase | 连接字符串实例的名称,此实例仅用于应用程序调用不带实例名的 DatabaseFactory.CreateDatabase 方法。 |
providerMappings 子元素这是一个
dataConfiguration 元素的子元素,只有在通过派生自 ADO.NET 的
Database 类而不是
GenericDatabase 类的提供程序时才需要指定提供程序的映射。SQL Server 和 Oracle 数据库默认已配置,所以不需要再在此节中指定。指定在此节中的一个数据库示例是 SQL Server CE 。
add 子元素add 是
providerMappings 元素的子元素。
add 元素添加一个数据库连接。此元素不是必须的,可以有多个
add 元素。
属性表 3 列出了
add 元素的属性
表 3 :add 的属性| 属性 | 描述 |
|---|
| databaseType | 派生自 Database 类的类型名,此属性是必须的。 |
| name | 使用的 ADO.NET 提供程序类型名称。名称必须在 DBProviderFactory 类中指定。此属性是必须的。 |
oracleConnectionSettings只有在需要指定 Oracle 数据库包映射时才需要此元素。
add 子元素add 元素是
oracleConnectionSettings 元素的子元素。
add 元素添加一个 Oracle 连接字符串实例。此元素不是必须的。可以有多个
add 元素。
属性表 4 列出了
add 元素的属性
表 4 :add 的属性| 属性 | 描述 |
|---|
| name | Oracle 连接字符串实例的名称。此属性是必须的。 |
packages 子元素这是
add 元素的一个子元素,指定一个 Oracle 的包。此元素是必须的。
add 子元素这是
packages 元素的一个子元素。
add 元素添加一个 Oracle 的包。此元素不是必须的。可以有多个
add 元素。
属性表 5 列出了
add 子元素的属性。
表 5 :add 的属性| 属性 | 描述 |
|---|
| Name | Oracle 包的名称。此属性是必须的。 |
| Prefix | Oracle 包的前缀。此属性是必须的。 |
顶部编辑2.2 - 添加应用程序代码
数据访问应用程序块为支持绝大多数访问数据库场景而设计。在添加自己的应用程序代码时,请参考在关键场景节中的场景,然后选择一种与自己的情况最匹配的方法。使用场景中的代码,或者如果需要,修改它以适合自己的需要。
首先,必须准备自己的应用程序以使用数据访问应用程序块。
准备应用程序- 添加到数据访问应用程序块程序集的引用。在 Visual Studio 中,在解决方案管理器中右单击项目节点,然后单击添加引用。单击浏览标签,然后导航到 Microsoft.Practices.EnterpriseLibrary.Data.dll 的位置。选择程序集,然后单击确定以添加引用。
- 按同样的步骤,添加到 Enterprise Library 内核程序集 Microsoft.Practices.EnterpriseLibrary.Common.dll 和 Microsoft.Practices.ObjectBuilder.dll 的引用。
- (可选的) 要不带完整的精确的元素引用使用来自加密应用程序块的元素,可以添加下列的 using 语句(C#)或者 Imports 语句(Visual Basic)到源代码文件的顶部。
C#
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data;
Visual Basic
Imports Microsoft.Practices.EnterpriseLibrary.Data
Imports System.Data
注意:对于 Visual Basic 项目,可以使用项目设计器的引用页来管理引用和导入命名空间。要访问引用页,在解决方案浏览器中选择项目节点,在项目菜单中单击属性。在项目设计器出现时,单击引用标签。
创建 Database 对象所有数据访问方法都相对于
Database 的对象。可以使用
DatabaseFactory 来创建
Database 对象。由工厂生成的
Database 对象的特定类型是由应用程序的配置信息决定的。
可以使用配置控制台指定一个默认的数据库实例。在不传递数据库实例名调用
CreateDatabase 方法时,
DatabaseFactory 创建由默认实例指定的 database 。下列应用程序代码展示了如何创建一个默认实例的
Database 对象。
C#
Database db = DatabaseFactory.CreateDatabase();
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
另一种方法是,应用程序代码可以指定一个命名的数据库实例。例如,如果使用配置控制台创建了名为 "Sales" 的实例,则为特定实例创建
Database 对象的代码可能如下。
C#
Database db = DatabaseFactory.CreateDatabase("Sales");
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase("Sales")
如果要创建的数据库的连接字符串是已知的,也要以放弃应用程序的配置信息,而是使用构造函数直接创建
Database 对象。因为
Database 类是一个抽象基类,所以必须构建一个它的派生类型。派生的
Database 类型决定了 ADO.NET 数据提供程序。例如,
SqlDatabase 类使用
SqlClientFactory 提供程序,
SqlCeDatabase 使用
SqlCeProviderFactory 提供程序,以及
OracleDatabase 使用
OracleClientFactory 提供程序。为连接字符串构建正确类型的
Database 类是你的责任。
下列代码使用提供的连接字符串创建了一个
SqlDatabase 对象。
C#
// Assume the method GetConnectionString exists in your application and
// returns a valid connection string.
string myConnectionString = GetConnectionString();
SqlDatabase sqlDatabase = new SqlDatabase(myConnectionString);
Visual Basic
' Assume the method GetConnectionString exists in your application and
' returns a valid connection string.
Dim myConnectionString As String = GetConnectionString()
Dim db As SqlDatabase = New SqlDatabase(myConnectionString)
如果通过不是 ADO.NET SQL 数据提供程序或者 Oracle 数据提供程序的数据提供程序使用一个连接字符串,可以创建一个
GenericDatabase 对象。在创建
GenericDatabase 对象时,必须支持
DbProviderFactory 对象。
选择适当的重载方法每个数据访问方法都有多个重载。下列描述和指南可以帮助你选择合适的重载:
- 有可以接受 ADO.NET 2.0 DbCommand 对象的重载。这些重载为每个方法提供了最大的灵活性和控制。
- 有接受存储过程名称和用于存储过程参数值的值集合的重载。这些重载在应用程序调用仅有输入参数的存储过程时比较方便。
- 有接受 System.Data.CommandType 和表示命令的字符串的重载。这些方便的重载在应用程序执行不带参数的内联 SQL 语句或存储过程时使用。
- 最后,以上每个重载都包含一个接受一个事务的重载。这允许在一个已存在的事务中执行方法时使用需要的重载类型。
每个关键场景示范了特定方法可用重载之一,许多场景都可以使用其他可用的重载完成。
顶部
编辑3 - 开发任务的细节
本节提供了关于下列在关键场景中描述的任务的更详细的信息:
- 创建一个 Database 对象。
- 使用 GetStoredProcCommand 方法或者 GetSqlStringCommand 方法创建 DbCommand 对象。
- 使用参数。
- 打开和关闭连接。
- 处理异常。
顶部编辑3.1 - 创建 Database 对象的细节
可以使用工厂创建一个 Database 对象或者直接构建一个。工厂使用配置信息决定连接字符串、ADO.NET 数据提供程序和要构建的适当的派生自数据访问应用程序块 Database 的对象。另一种时,传递所有需要的信息给对象的构造函数直接创建 Database 对象。
在要使用由 Enterprise Library 支持的保存在某个位置的配置信息时或者使用由 ADO.NET 管理的连接字符串时使用工厂。例如,使用工厂用保存在应用程序配置文件中的 <connectionStrings> 节中的连接字符串信息创建 Database 对象。也可以使用工厂用保存在另一个配置源中的连接字符串构建一个 Database 对象。必须使用另一个默认配置源来配置应用程序,以允许工厂用保存在那个配置源中的连接字符串创建对象。在从不是默认配置源中的某些源中获取连接字符串时,可以使用构建函数。
CreateDatabase 方法是 DatabaseFactory 类的一个静态方法。工厂基于配置文件中的信息创建一个正确的 database 类,并返回基类的子类的对象:Database 到客户代码。除非需要特定于特殊数据库类型的命令,例如 SQL Server ,否则应该仅使用 Database 基类的的可用方法以保持应用程序所使用的数据库是不可知的。特定的数据库派生类型的创建对应用程序代码而言是透明的,因此,同样可以不需要考虑所使用的数据库类型。
可以使用 CreateDatabase 方法来基于默认配置指定创建的数据库类型。通过修改默认配置,可以使未经修改的应用程序运行于不同的数据库。
也可以使用命名的数据库实例,例如在应用程序中的 "Customers" 。工厂使用配置文件中的连接字符串来查找与特定命名实例相关的信息以创建正确的数据库类型。
如果需要使用某个或另一个数据库特定的命令,就必须通过向下类型转换(downcasting)指定期望由工厂创建的数据库类型。
最后,可以忽略应用程序的配置信息直接创建一个 DataBase 对象子类型的 Database 对象。要做到这一点,必须知道要创建的数据库的类型,以及连接字符串和其他任何子类型需要的信息。
创建默认数据库不指定参数的调用工厂的
CreateDatabase 方法将创建一个默认的 database 。配置文件决定哪个命名实例是默认实例。可以使用配置控制台修改默认实例。
下列代码展示了如何创建一个标记为默认实例的 database 对象。
C#
Database dbSvc = DatabaseFactory.CreateDatabase();
Visual Basic
Dim dbSvc As Database = DatabaseFactory.CreateDatabase()
注意
如果配置文件没有指定默认实例,并且客户代码在调用 CreateDatabase 方法时也没有指定参数,应用程序块将抛出异常。
使用实例要使用实例,可以通过逻辑名称在应用程序代码中引用 database ,并且修改数据库配置信息(如位置或连接字符串信息)而不用重新编译代码。
下列示例展示了如何使用名称“Sales”创建 database 。
C#
// Use a named database instance that refers to an arbitrary database type,
// which is determined by configuration information.
Database myDb = DatabaseFactory.CreateDatabase("Sales");
Visual Basic
' Use a named database instance that refers to an arbitrary database type,
' which is determined by configuration information.
Dim myDb As Database = DatabaseFactory.CreateDatabase("Sales")
创建特定的 Database 类型如果必须使用专用于特定数据库类型的方法,在创建数据库时可以指定 database 类型。下列代码要求工厂创建一个
SqlDatabase 对象。
C#
// Create a SQL database.
SqlDatabase dbSQL = DatabaseFactory.CreateDatabase("Sales") as SqlDatabase;
Visual Basic
' Create a SQL database.
Dim dbSQL As SqlDatabase = DirectCast(DatabaseFactory.CreateDatabase("Sales"), SqlDatabase)
同样,要创建一个 Oracle 数据库,使用
OracleDatabase 。要创建一个 SQL CE 数据库,使用 SqlCeDatabase 类型。
创建一个数据库而不使用配置可以通过提供一个连接字符串给 database 类构造函数来不使用配置数据的创建一个数据库对象。下列代码展示了如何创建一个 SqlDatabase 对象。
C#
// Assume your application contains the routine GetConnectionString.
string myConnectionString = GetConnectionString();
SqlDatabase sqlDatabase = new SqlDatabase(myConnectionString);
Visual Basic
' Assume your application contains the routine GetConnectionString.
Dim myConnectionString As String = GetConnectionString()
Dim db As SqlDatabase = New SqlDatabase(myConnectionString)
下列代码展示了如何创建一个 GenericDatabase 对象。必须提供连接字符串和 DbProviderFactory 对象。在这种情况下,DbProviderFactory 对象是 OdbcFactory 。
C#
GenericDatabase db = new GenericDatabase(connectionString, OdbcFactory.Instance);
Visual Basic
Dim db As GenericDatabase = New GenericDatabase(connectionstring, OdbcFactory.Instance)
使用 SQL Server CESQL Server CE 是一个小型的、进程内的数据库,它提供了关系数据库的必须功能,目的在于需要本地数据存储但不需要 SQL Server 的完整功能的桌面和移动应用程序。每个数据库都保存在一个文件中,默认情况下,扩展名为 .sdf 。使用
CreateFile 方法可以创建一个新的空数据库,此方法使用来自连接串的文件名。
对于 SQL Server CE ,打开一个连接就是打开数据库文件。结果是,为每个请求创建和释放连接将非常缓慢。为了避免这些性能问题,使用 SQL Server CE 的应用程序通常在使用数据库期间尽可能长的保存连接打开。
在第一次调用
Database 类的方法时,提供程序创建一个附加的 “keep alive”连接,它在内存中保持了数据库引擎。应用程序为每个
Database 类方法的调用打开和关闭其他的连接,但关闭这些连接不会关闭 “keep alive”连接。
要打开一个数据库,使用
CreateConnection 方法打开到它的连接。这个方法创建了 “keep alive”连接。当使用完数据库后,必须使用
CloseSharedConnection 方法关闭到数据库的 “keep alive”连接。对于每个连接字符串仅有一个 “keep alive”连接,尽管对于同样的连接字符串可以有多个打开的连接。
因为 SQL Server CE 是一个进程内的数据库,对数据库的多个调用将是快而有效的。SQL Server CE 不支持存储过程。如果试图使用任何
Execute 方法,如
ExecuteScalar 和
ExecuteNonQuery ,以一个存储过程做为参数的话,应用程序块将抛出异常。不用存储过程,可以使用内联的 SQL 语句来代替。在此有些 Execute 方法的重载是接受一个 SQL 语句为参数的。因为存储过程不受支持的同样原因,只能在一个请求中发送一条 SQL 语句。
SQL Server CE 有一个名为
SqlCeResultSet 的特殊结果集。这是查询返回的结果集类型。它支持在数据库中的查询、前向和后向移动、以及修改数据。
关于 SQL Server CE 的一般信息,请参见 Microsoft Web 站点上的
Microsoft SQL Server: SQL Server 2005 Compact Edition 。相关 API 的信息,请参见 MSDN 上的
System.Data.SqlServerCe 命名空间页。
注意
SQL Server CE 仅在完全信任环境中操作。
通过 TransactionScope 类使用 Oracle尽管可以通过 Oracle 客户端来使用
TransactionScope 类,但事务总是被处理为分布式事务而不是轻量级的事务。分布式事务有较高的性能开销。
.NET Framework 托管的用于 Oracle 的提供程序需要一个名为 oramts.dll 的文件以使用
TransactionScope 类。更多信息,请参见 Microsoft 帮助和支持 Web 站点。
如果通过 Microsoft 事务服务器使用 Oracle,请参见 Oracle Web 站点上的
http://www.oracle.com/technology/software/tech/windows/ora_mts/htdocs/utilsoft.html 以获得适当的下载。
使用提示DatabaseFactory 对象基于 ADO.NET 的
DbProviderFactory 对象决定哪种
Database 对象被创建,
DbProviderFactory 与连接字符串相关联。连接字符串保存在配置文件的 <connectionStrings> 节中。默认情况下,应用程序块为
System.Data.SqlClient 类型的数据提供程序创建
SqlDatabase 类型的
Database 对象,为
System.Data.SqlServerCe 类型的数据提供程序创建
SqlCeDatabase 类型的对象,为
System.Data.OracleClient 类型的数据提供程序创建
OracleDatabase 类型的对象,为其他所有数据提供程序类型创建
GenericDatabase 类型的对象。
GenericDatabase 类仅支持由 ADO.NET 提供功能的数据库提供程序。特别的,支持参数发现的数据访问重载无法工作。
GenericDatabase 可以由任何 .NET 托管的提供程序使用,包括 .NET Framework 2.0 中的 ODBC 和 OLE-DB 提供程序。可以通过在配置文件中的配置设置来覆盖数据提供程序类型和
Database 对象类型之间的映射。更多信息,请参见数据访问应用程序块的设计。
顶部
编辑3.2 - 创建 DbCommand 对象
数据访问应用程序块提供了获取 ADO.NET
DbCommand 对象的的统一方法。应用程序块的数据访问方法包含了接受
DbCommand 对象的重载。如果用
DbCommand 对象来使用重载,在调用存储过程时将可以进行更多的控制。例如,如果使用
DbCommand 对象,就可以使用在输出参数中返回多个结果的存储过程。另外,
DbCommand 对象允许指定存储过程的超时值。
创建
DbCommand 对象的方法分为二种类型:
- 表示存储过程调用的那些方法(例如,GetCustomers)
- 表示 SQL 文本命令的那些方法(例如,Select CustomerID, Fullname From Customers )
调用的获取
DbCommand 对象的方法由是要执行内联的 SQL 还是调用存储过程来决定。用于存储过程的创建
DbCommand 对象的方法还提供参数缓存。关于参数缓存的更多信息,请参见处理参数。
所有
DbCommand 对象的创建都使用
Database 类的方法,这些方法如下:
- GetStoredProcCommand。此方法用于存储过程命令。
- GetSqlStringCommand。此方法用于 SQL 文本命令。
二个方法都返回一个
DbCommand 对象。
注意:SQL Server CE 不支持存储过程,用内联 SQL 语句来代替。更多信息,请参见创建 Database 对象的细节。
用于 SQL 语句的 DbCommand对象使用
GetSqlStringCommand 方法创建用于内联 SQL 语句的
DbCommand 对象。特定的 SQL 命令在方法调用时做为一个参数进行传递。
下列代码展示了如何使用
GetSqlStringCommand。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "Select CustomerID, LastName, FirstName From Customers";
DbCommand dbCommand = db.GetSqlStringCommand(sqlCommand);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As String = "Select CustomerID, LastName, FirstName From Customers"
Dim dbCommand As DbCommand = db.GetSqlStringCommand(sqlCommand)
用于存储过程的 DbCommand 对象要执行存储过程,必须使用
GetStoredProcCommand 方法来创建
DbCommand 对象。要执行存储过程的名称在方法调用时做为一个参数传递。
下列代码展示了如何使用
GetStoredProcCommand。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory");
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand
dbCommand = db.GetStoredProcCommand("GetProductsByCategory")
注意:存储过程的参数受 Database 类的方法的支持。关于如何使用存储过程参数参数的更多信息,请参见处理参数。
顶部编辑3.3 - 管理连接
数据库连接是有限资源,它们的妥善管理对可扩展的应用程序来说是必不可少的。仅在需要时保持连接打开并尽快关闭是一个很好的实践。根据设计,绝大多数的
Database 类方法在每次调用时打开和关闭到数据库的连接。因为,应用程序代码不需要包含用于管理连接的代码。(默认情况下,基于性能的原因,ADO.NET 将连接返回到连接池中,而不是关闭他们。因此,不需要缓存
Database 对象。)
例如,
ExecuteDataSet 返回包含所有数据的
DataSet 对象。这给了你一个自己的本地副本。对
ExecuteDataSet 的调用打开了一个连接、组装了一个
DataSet、然后在返回结果前关闭连接。
下列代码示范了
ExecuteDataSet 方法的使用。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "Select ProductID, ProductName From Products";
DbCommand dbCommand = db.GetSqlStringCommand(sqlCommand);
// No need to open the connection; just make the call.
DataSet customerDataSet = db.ExecuteDataSet(dbCommand);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As String = "Select ProductID, ProductName From Products"
Dim dbCommand As DbCommand = db.GetSqlStringCommand(sqlCommand)
' No need to open the connection; just make the call.
Dim customerDataSet As DataSet = db.ExecuteDataSet(dbCommand)
然而,在关闭连接时有一些未清理的其他情况。一个例子就是
ExecuteReader 方法。此方法返回实现
IDataReader 接口的对象。
Database 基类有一个返回
DbDataReader 对象的默认实现。
DbDataReader 对象被设计用来读取需要的数据的特定部分,它需要一个打开的连接。换句话说,它不知道应用程序何时不再需要
DbDataReader 。如果数据访问应用程序块在返回
DbDataReader 之前就关闭了连接,
DbDataReader 对客户代码而言是无用的。因此,
DbDataReader 方法指定底层的 ADO.NET 在
DbDataReader 完成后自动关闭连接。在这种情况下,它被认为是由应用程序确定
DbDataReader 及时关闭的最好方法,可以使用
DbDataReader.close 方法显示的关闭
reader 或者强制
DbDataReader 的销毁,这是
Close 方法被调用的结果。
下列代码示范了对
ExecuteReader 方法的调用。
using 语句(在 Visual Basic 中为
Using )确保
DbDataReader 对象被销毁,并在销毁的过程中关闭
DbDataReader 对象。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetSqlStringCommand("Select Name, Address From Customers");
using (IDataReader dataReader = db.ExecuteReader(dbCommand))
{
// Process results
}
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand = db.GetSqlStringCommand("Select Name, Address From Customers")
Using dataReader As IDataReader = db.ExecuteReader(dbCommand)
' Process results
End Using
顶部编辑3.4 - 使用 TransactionScope 类
在此对
Database 类的某些方法进行了一些修改,以利用 .NET Framework 2.0 的
TransactionScope 类。此类自动将数据库调用加入到一个外围的事务中。这在将业务对象加入到一个事务中而不传递事务到这些业务对象中时非常有用。以下是
TransactionScope 类的使用的基本模型。
C#
using (TransactionScope scope = new
TransactionScope(TransactionScopeOption.RequiresNew))
{
int rows = db.ExecuteNonQuery(CommandType.Text, insertString);
rows = db.ExecuteNonQuery(CommandType.Text, insertString2);
}
二个
ExecuteNonQuery 方法将行插入到了在创建
TransactionScope 实例时定义的事务中。
TransactionScope 类创建了一个本地的、轻量级的事务。它假定为发生在事务中的所有的数据库调用使用一个连接。这意味着,做为传递
DbTransaction 实例的另一种方法,简单的传递连接,然后 .NET Framework 自动为执行的每个命令设置了连接。
Enterprise Library,换句话说,通常为每个请求打开并关闭连接。此方法与
TransactionScope 类工作的方法不兼容。如果有多个连接,
TransactionScope 类将认为事务是分布式事务。分布式事务比本地事务有显著的性能和资源消耗。
要避免这些,
Database 类的方法,如
ExecuteDataSet ,识别
TransactionScope 实例活动的时机,并添加 database 调用到此事务中。如果事务的当前活动是使用
TransactionScope 实例的结果,
Database 类方法会使用单一的连接。
特别的,
GetOpenConnection 方法替换了
Database 方法中的
OpenConnection 方法,
GetOpenConnection 方法返回一个连接包装器。如果没有事务正在处理,方法将销毁包装器。然而,当事务还在处理中时,方法将保持连接打开。
如果使用
ExecuteXmlReader 方法,将测试看
TransactionScope 实例是否是活动的。此方法将返回保持 reader 使用的连接的
XmlReader 对象。当 reader 使用结束后,最后的方法就是关闭此连接。然而,如果使用的是
TransactionScope 的实例,必须不能这么做,因为关闭此连接并创建一个新的连接将会改变轻量级的事务为分布式事务。
注意:多线程中共享在一个 transaction scope 中的同一事务将导致下列异常:“Transaction context in use by another session.”
顶部编辑3.5 - 创建可移植的数据库应用程序
如果应用程序必须工作在多个数据库类型下,有些问题就必须要考虑。
Oracle如果使用 LoadDataSet 方法加载数据,它将不会转换 Guid 和 Boolean 数据类型。这是因为架构无法决定数据的值是 Guid 还是简单的 bype
">">。数据将返回为 byte[ 列。
当你为返回多个游标的存储过程创建 DbCommand 对象时,必须传递一个对象数组到 GetStoredProcCommand 方法。数组的大小必须与由存储过程返回的游标数量相同。例如,下列代码示范了如何为返回二个游标的存储过程传递对象数组到 GetStoredProcCommand 。
C#
Database db = DatabaseFactory.CreateDatabase();
object results = new object2;
DbCommand dbCommand = db.GetStoredProcCommand("GetCustomersAndSuppliers", results);
Visual Basic
Dim results As Object = New Object(2) {}
Dim dbCommand As DbCommand = db.GetStoredProcCommand("GetCustomersAndSuppliers", results)
如果存储过程仅返回一个游标,则不必须传递对象数组。
用于创建可移植数据库应用程序的建议。在此有一些用于创建可移植数据库应用程序的建议:
- 避免用存储过程参数名使用数据库专用令牌。用于特定提供程序的 Database 派生类包含了调整需要的参数名的代码。例如,在支持到 SQL Server 数据库的存储过程参数名中不要包含 "@" 字符。下列代码展示了如何调用 AddInParameter 方法通过名称 CategoryID 创建参数。当使用 SqlDatabase 对象执行此代码时,提供程序用 "@" 做为参数名的开头。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory");
db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, 100);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand = db. GetStoredProcCommand("GetProductsByCategory")
db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, 100)
- 总是通过 database 对象获取参数值。
- 考虑后端关系数据库管理系统(RDBMS)的大小写敏感。例如,在 SQL Server 2000 中的字符串比较是大小写不敏感的,但是在 Oracle 8i 和 DB2 中是大小写敏感的。要开发一个可移植的应用程序,就必须编写自己的比较逻辑为大小写不敏感的或者强迫应用程序仅为在比较操作中使用的列存储大写或小写。
- 避免使用 RDBMS 专用数据类型,例如 OracleBlob。
- 在执行存储过程时避免使用返回值,而是使用输出参数。
- 在添加参数到参数集合中时,确认在应用程序代码中的顺序与数据库中的顺序相匹配。OLE DB 提供程序使用顺序来执行存储过程而不是名称,所以以正确的顺序添加集合是很重要的。
- 如果在应用程序代码必须使用内联的 SQL ,确认 SQL 语法对于应用程序将运行的数据库类型都是可用的。
- 避免传递 null 值到值类型的存储过程参数。如果需要通过 SQLJ 存储过程使用 DB2 的可移植接口,这些做将可能无法正常工作。
顶部编辑3.6 - 处理异常
处理异常的策略在任何企业应用程序中都是必不可少的。下列信息将帮助你添加数据访问应用程序块到管理异常的方法中去:
- CreateDatabase 方法使用配置信息,其可能的结果在配置相关的异常中。
- Database 方法使用 ADO.NET 和底层数据库提供程序。由 ADO.NET 抛出的异常由数据访问应用程序块为度量的目的而捕获,然后再次抛出。
- 充分处理异常通常要求访问特定的异常类型。可以包含用于特定数据库提供程序的异常如 SqlException 的 catch 语句。然而数据库提供程序专用的异常类型在不同提供程序之间不能移植。
- 使用 CommandBehavior.CloseConnection 调用 ExecuteReader。它在 DataReader 关闭时关闭连接。如果在一个 try 块中使用 ExecuteReader ,可以添加一个 finally 语句并关闭返回的 DataReader 对象,就像展示在下列示例中的一样。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory");
IDataReader dataReader = null;
try
{
//...
dataReader = db.ExecuteReader(dbCommand);
}
catch(Exception ex)
{
// Process exception
}
finally
{
if (dataReader != null)
dataReader.Close();
}
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand = db.GetStoredProcCommand("GetProductsByCategory")
Dim dataReader As IDataReader = Nothing
Try
' ...
dataReader = db.ExecuteReader(dbCommand)
Catch ex As Exception
' Process exception
Finally
If (Not dataReader Is Nothing) Then
dataReader.Close()
End If
End Try
另一种方法是,可以包含
using 语句来销毁
DataReader 对象,这将导致它的关闭,就像展示在下列示例中一样。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory");
using (IDataReader dataReader = db.ExecuteReader(dbCommand))
{
// Process results
}
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand = db.GetStoredProcCommand("GetProductsByCategory")
Using dataReader As IDataReader = db.ExecuteReader(dbCommand)
' Process results
End Using
对于在 .NET 中的异常管理的设计和实现原则,请参见
异常管理架构指南。
顶部编辑3.7 - 处理参数
绝大多数存储过程接受用于输入存储过程或在输出时设置的值的参数。就像使用 ADO.NET 一样,数据访问应用程序块允许开发人员指定参数所有的属性。这些属性可以包括方向、数据类型和长度。此方法叫做显式参数处理。然而,为了方便,可以仅指定用于输入参数的值。在这种情况下,应用程序块将查找并提供参数的属性。此方法叫参数发现。
显式参数处理Database 类包含了不同的用于传递参数到存储过程的方法。此类还包含了用于设置和测试这些参数的值的方法。这些方法如下:
- AddParameter。此方法传递一个参数(输入或输出)到存储过程。
- AddInParameter。此方法传递输入参数到一个存储过程。
- AddOutParameter。此方法添加了一个输出参数到存储过程。
- GetParameterValue。此方法查找指定的参数的值。
- SetParameterValue。此方法在使用同样的连接和命令,但有不同的参数值时进行多个插入时设置指定参数的值。
下列代码示范了如何使用
AddInParameter 和
AddOutParameter 指定参数。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "GetProductDetails";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);
db.AddInParameter(dbCommand, "ProductID", DbType.Int32, 5);
db.AddOutParameter(dbCommand, "ProductName", DbType.String, 50);
db.AddOutParameter(dbCommand, "UnitPrice", DbType.Currency, 8);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As String = "GetProductDetails"
Dim dbCommand As DbCommand = db.GetStoredProcCommand(sqlCommand)
db.AddInParameter(dbCommand, "ProductID", DbType.Int32, 5)
db.AddOutParameter(dbCommand, "ProductName", DbType.String, 50)
db.AddOutParameter(dbCommand, "UnitPrice", DbType.Currency, 8)
注意:前面的代码不包括专用于数据库类型的参数名称令牌。因此,代码保留了跨多个不同数据库提供程序的通用性。当此代码运行于 SqlClient 数据提供程序时(并因此使用 SqlDatabase 类),下列代码将与前面的代码有着同样的行为。然而,此代码不能移植到其他的数据类型。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "GetProductDetails";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);
db.AddInParameter(dbCommand, "@ProductID", DbType.Int32, 5);
db.AddOutParameter(dbCommand, "@ProductName", DbType.String, 50);
db.AddOutParameter(dbCommand, "@UnitPrice", DbType.Currency, 8);
Visual Basic
Dim sqlCommand As String = "GetProductDetails"
Dim dbCommand As DbCommand = db.GetStoredProcCommand(sqlCommand)
db.AddInParameter(dbCommand, "@ProductID", DbType.Int32, 5)
db.AddOutParameter(dbCommand, "@ProductName", DbType.String, 50)
db.AddOutParameter(dbCommand, "@UnitPrice", DbType.Currency, 8)
使用列值做为参数输入UpdateDataSet 方法要求三个不同的命令:一个用于插入值,一个用于修改值,另一个用于删除值。通常,这些命令用于存储过程而不是 SQL 字符串。它们在调用后保持由存储过程使用的参数。代替指定用于存储过程参数的值,来自
DataSet 的值被用作输入。在这种情况下,
AddInParameter 的适当重载是接受源列做为参数的方法之一。
下列代码展示了如何使用列值做为参数输入。
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand insertCommand = db.GetStoredProcCommand("AddProduct");
db.AddInParameter(insertCommand, "ProductName", DbType.String, "ProductName", DataRowVersion.Current);
db.AddInParameter(insertCommand, "CategoryID", DbType.Int32, "CategoryID", DataRowVersion.Current);
db.AddInParameter(insertCommand, "UnitPrice", DbType.Currency, "UnitPrice", DataRowVersion.Current);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim insertCommand As DbCommand = db.GetStoredProcCommand("AddProduct")
db.AddInParameter(insertCommand, "ProductName", DbType.String, "ProductName", DataRowVersion.Current)
db.AddInParameter(insertCommand, "CategoryID", DbType.Int32, "CategoryID", DataRowVersion.Current)
db.AddInParameter(insertCommand, "UnitPrice", DbType.Currency, "UnitPrice", DataRowVersion.Current)
参数发现使用数据访问应用程序块,开发人员可以指定用于参数的值,而不需要关于这些参数的任何其他信息。在使用参数发现时,将要指定所有参数,并设置所有输出参数为
NULL 。
下列代码示范了如何仅通过指定参数值而无其他属性来使用
GetStoredProcCommand 。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "UpdateProduct";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand, 11, "Queso Cabrales", 4, 25);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As string = "UpdateProduct"
Dim dbCommand As DbCommand = db.GetStoredProcCommand(sqlCommand, 11, "Queso Cabrales", 4, 25)
关于每个参数的信息(例如,它的数据类型)依赖是底层 ADO.NET 方法调用所需要的。为了提供这些信息,数据访问应用程序块使用 ADO.NET 中的
DeriveParameters 方法来查找参数信息。
因为
DeriveParameters 调用需要到后端数据库的一次往返,应用程序块还提供了参数信息缓存。在第一次调用需要参数发现的特定存储过程后,关于每个参数的信息都保存到了参数缓存中。这意味着对同样的存储过程的后继调用将不需要往返。
在使用参数发现时,最好的方法是指定所有的输出参数为
NULL 。不需要为 Oracle 存储过程提供游标参数,
OracleDatabase 提供了它们。此对象假设游标参数是存储过程参数列表中的第一个参数。
顶部编辑4 - 关键场景
此主题描述了开发人员在访问数据库时必须解决的绝大多数常见情况。每个场景都解释了任务,描述了任务可能出现的真实世界的情况,以及包括示范如何使用数据访问应用程序块完成任务的代码。场景如下:
- 使用 DbDataReader 获取多行数据。此场景示范了如何能使用 ExecuteReader 方法从数据库中获取多行数据以用表格的形式显示,没有显式的缓存数据,使用 DataSet 对象来操作它,或者将它传递给应用程序中的其他组件。换句话说,它示范了如何尽快的显示结果。
- 使用 DataSet 来获取多行数据。此场景示范了如何能使用 ExecuteDataSet 方法在组件和多层应用程序层之间传递数据。数据以一个或多个数据表组件,以及可选的将数据链接在一起的关系。
- 执行一个命令并访问输出参数。此场景示范了如何使用 ExecuteNonQuery 方法来获取包含多个列值的单行数据。
- 执行一个命令并访问单项结果。此场景示范了如何使用 ExecuteScalar 方法来完成单项查询。
- 在一个事务中完成多个更新。此场景示范了如何在一个事务中使用 ExecuteNonQuery 中完成对一个数据库的多个操作,它本质上是所有操作要么全部成功,要么全部不成功。
- 使用 DataSet 更新数据库。此场景示范了如何在修改 DataSet 对象后,可以使用 UpdateDataSet 方法来更新数据库,以使修改持久保存。
- 获取多行数据为 XML 。此场景示范了如何使用 ExecuteXmlReader 方法从 SQL Server 中获取数据,并得到以 XML 格式返回的数据。
此文档不能帮助你选择用于特殊情况的正确方法(例如,无法帮助你在
DataSet 和
DbDataReader 之间做出选择),而是帮助你使用数据访问应用程序块实现自己的方法。对于数据访问方法的指导,请参见下列的 Microsoft patterns & practices 指南:
顶部
编辑4.1 - 使用 DbDataReader 获取多行数据
一种常见的数据库任务是获取并显示信息。例如,一个在线的零售应用程序可能需要显示特定分类中的产品的列表。
典型目标在此场景中,要从数据库中获取多个数据行并立刻在浏览器显示且仅显示一次,不用显式的缓存数据,不需要用 DataSet 对象操作它,或者将它传递到应用程序中的其他组件。只是简单的尽快使用结果。
目标可以总结如下:
- 为只读的目的获取数据,这在大多数情况下是要显示数据。
- 绑定到 Web Form 控件以显示。
- 不需要要缓存数据。在使用后,数据将被销毁。
解决方案用存储过程使用
ExecuteReader 方法(由
Database 类提供)。例如,如果应用程序使用在线的分类,就可以传递一个分类的 ID 到存储过程,以指出要获取的产品集。
ExecuteReader 方法返回实现了
IDataReader 接口的对象。
Database 类的
ExecuteReader 实现返回一个
DbDataReader 对象。
DbDataReader 支持数据绑定,并可以做为许多 ASP.NET 服务器控件的数据源,例如
DataList 或
DataGrid 控件。这提供了有效且灵活的在浏览器输出结果的方法。
快速入门对于如何使用
ExecuteDataReader 获取多行数据的扩展示例,请参见快速入门漫游,漫游:使用 DbDataReader 获取多行。
使用 ExecuteDataReader下列代码展示了如何用 SQL 语句使用 ExecuteDataReader 方法。
C#
Database db = DatabaseFactory.CreateDatabase();
using (IDataReader dataReader = db.ExecuteReader(CommandType.Text, "Select Name, Address, City From Customers" ))
{
customerGrid.DataSource = dataReader;
customerGrid.DataBind();
}
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Using dataReader As IDataReader = db.ExecuteReader(CommandType.Text, "Select Name, Address, City From Customers")
customerGrid.DataSource = dataReader
customerGrid.DataBind()
End Using
还有其他的可用重载允许开发人员以不同的方式调用
ExecuteReader 方法。可用的重载类型和影响将使用哪个重载的因素的描述,请参见添加应用程序代码。
使用提示ExecuteReader 方法打开到数据库的连接,并使用
CommandBehavior.CloseConnection 方法来合并连接的生命周期和 reader 。因此,在它完成后,必须马上关闭读取器。关闭读取器将导致连接关闭并返回到连接池中去(假设没有显式的关闭连接池)。使用在示例中的语句确保释放读取器。
顶部编辑4.2 - 使用 DataSet 获取多行数据
在多层系统中,可能需要从数据访问组件传递数据到中间层业务组件。数据从数据库被取出并发送回来,穿过数据访问层,到达业务层。信息被包含在 DataSet 对象中。
典型目标当在多层系统中访问数据时,通过有下列目标之一:
- 要获取多个表或者从不同的数据源中获取表。
- 要与其他应用程序或者组件如 XML Web 服务交换数据。
- 不得不完成对从数据库中获取的每个记录的昂贵处理。如果使用数据命令和数据读取器,在读取时处理每条记录,会导致连接长时间打开,然后会影响应用程序的性能和可扩展性。
- 为数据处理访问相互依赖的记录(例如,在相关记录中查找信息)。
- 要完成 XML 操作,例如在数据上的 XSLT 转换。
解决方案使用
DataSet 对象达到这些目的是一种很好的途径。ADO.NET
DataSet 是一个数据容器,它由一个或多个数据表组成,以及可选的将表链接在一起的关系。它是一个非连接的对象,并且没有任何底层数据源的信息。它支持数据的 XML 操作,而且还是在组件和多层应用程序的层之间传递数据的理想运输工具。
快速入门对于如何使用
ExecuteDataSet 方法获取多行数据的扩展示例,请参见快速入门漫游,漫游:使用 DataSet 获取多行数据。
使用 ExecuteDataSet下列代码展示了如何用
DbCommand 对象使用
ExecuteDataSet 方法。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "GetProductsByCategory";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);
// Retrieve products from category 7.
int category = 7;
db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, category);
DataSet productDataSet = db.ExecuteDataSet(dbCommand);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As String = "GetProductsByCategory"
Dim dbCommand As DbCommand = db.GetStoredProcCommand(sqlCommand)
' Retrieve products from the category 7.
Dim category As Integer = 7
db.AddInParameter(dbCommand, "CategoryID", DbType.Int32, category)
' DataSet that will hold the returned results
Dim customerproductDataSet As DataSet = Nothing
customerproductDataSet = db.ExecuteDataSet(dbCommand)
还有其他的允许开发人员以不同的方式调用
ExecuteReader 方法的可用重载。对于可用的重载类型和影响将使用哪个重载的因素的描述,请参见添加应用程序代码。
使用提示在使用
ExecuteDataSet 方法的重载时考虑下列提示:
- 数据访问应用程序块用用于所包含的 DataTable 对象的默认名称生成 DataSet 对象,例如,Table、Table1 和 Table2。
- 如果要重新使用已存在的 DataSet 而不是新创建一个来保持查询的结果,可以使用 Database 类的 LoadDataSet 方法。
顶部编辑4.3 - 执行一个 Command 并访问输出参数
一个常见的数据库任务是获取特定的多列值。例如,基于 Web 的在线零售应用程序,可能要在用户请求的响应中为特定的商品获取完整的商品信息。
典型目标在此场景中的典型目标是从特定表中的一行或从不同表的多个相关行中获取特定的数据条目。
解决方案实现此目标的最有效的方法之一是使用存储过程的输出参数。例如,在一个在线分类中,存储过程接收一个产品 ID 做为输入参数,并通过一个输出参数值返回产品的详细信息。
要调用存储过程,可以使用
ExecuteNonQuery 方法,传递输入和输出参数。在方法返回时,输出参数将由获取的列值所组成。
快速入门关于如何使用
ExecuteNonQuery 方法获取多行数据的扩展示例,请参见快速入门漫游,漫游:执行一条命令并访问输出参数。
使用 ExecuteNonQuery下列代码展示了如何用传递
DbCommand 对象来使用
ExecuteNonQuery 方法。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "GetProductDetails";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);
db.AddInParameter(dbCommand, "ProductID", DbType.Int32, 3);
db.AddOutParameter(dbCommand, "ProductName", DbType.String, 50);
db.AddOutParameter(dbCommand, "UnitPrice", DbType.Currency, 8);
db.ExecuteNonQuery(dbCommand);
string results = string.Format(CultureInfo.CurrentCulture, "{0}, {1}, {2:C} ",
db.GetParameterValue(dbCommand, "ProductID"),
db.GetParameterValue(dbCommand, "ProductName"),
db.GetParameterValue(dbCommand, "UnitPrice"));
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As String = "GetProductDetails"
Dim dbCommand As DbCommand = db.GetStoredProcCommand(sqlCommand)
db.AddInParameter(dbCommand, "ProductID", DbType.Int32, 3)
db.AddOutParameter(dbCommand, "ProductName", DbType.String, 50)
db.AddOutParameter(dbCommand, "UnitPrice", DbType.Currency, 8)
db.ExecuteNonQuery(dbCommand)
Dim results As String = String.Format(CultureInfo.CurrentCulture, "{0}, {1}, {2:C} ", _
db.GetParameterValue(dbCommand, "ProductID"), _
db.GetParameterValue(dbCommand, "ProductName"), _
db.GetParameterValue(dbCommand, "UnitPrice"))
还有其他的可用重载允许开发人员以不同的方式调用
ExecuteReader 方法。可用的重载类型和影响将使用哪个重载的因素的描述,请参见添加应用程序代码。
使用提示在使用
ExecuteNonQuery 方法的重载时可以考虑以下方面:
- ExecuteNonQuery 方法返回查询所影响的行数(通常是 Insert、Update、或者 Delete 操作)。
- 可以使用 ExecuteNonQuery 方法修改数据库中的数据,而不需要通过执行 Insert、Update 或者 Delete 操作来使用 DataSet。
顶部编辑4.4 - 执行一个 Command 并访问单项结果
在许多情况下都要完成单项查询。例如,在线的零售商可能要使用产品 ID 获取产品的名称,或者使用用户 ID 获取信贷级别。
典型目标在本场景中的目标是返回一个单一值做为查询的结果。
解决方案返回单一值的有效方法是使用
ExecuteScalar 方法和唯一标识。例如,在一个在线的分类中,可以使用产品的 ID 来获取产品的名称或者可以使用客户的 ID 来获取客户的信贷级别。
快速入门对于如何使用
ExecuteScalar 获取单项结果的扩展示例,请参见快速入门漫游,漫游:执行一个命令并访问单项结果。
使用 ExecuteScalar下列代码展示了使用
ExecuteScalar 方法来传递
DbCommand 对象。
C#
Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "GetProductName";
int productId = 7;
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand, productId);
string productName = (string)db.ExecuteScalar(dbCommand);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim sqlCommand As String = "GetProductName"
Dim productId As Integer = 7
Dim dbCommand As DbCommand = db.GetStoredProcCommand(sqlCommand, productId)
Dim productName As String = DirectCast(db.ExecuteScalar(dbCommand), String)
还有其他的可用重载允许开发人员以不同的方式调用
ExecuteScalar 方法。可用的重载类型和影响将使用哪个重载的因素的描述,请参见添加应用程序代码。
用法提示在使用 ExecuteScalar 方法的重载时考虑下列几个方面:
在使用
ExecuteScalar 方法使用结果集返回一个 SQL Server @@Identity 变量时,要知道 SQL Server 返回的 @@Identity 值是 decimal 数据类型,而不是 integer 。如果需要获取的值是整数类型的,就必须在客户应用程序中使用代码进行转换。可以使用 Transact-SQL 的
CAST 函数来返回 integer 类型的值,就如下列示例中的一样。
SELECT CAST(@@Identity AS INTEGER)
顶部编辑4.5 - 在一个事务中完成多个更新
在应用程序执行相对一个数据库的多个操作时,常见的需求时所有的操作必须成功或者数据库必须回滚到初始状态(就是操作前的状态)。这种要么全部要么什么都不发生的需求被称为事务。事务确保数据库系统的状态的完整性。例如,在一个经典银行业的场景中,应用程序必须从一个帐户中转出,并将特定数量的钱转入另一个帐户。对于期望的帐户处理,本质上是二个操作要么成功,要么都不成功。这意味着二个操作都在单一的事务环境中完成。
典型目标在此场景中的典型目标是对一个数据库的所有操作必须成功或者都没完成。
解决方案有多次方法可以在一个事务中执行数据库方法。在此展示的解决方案示范了如何在一个手工创建的事务环境中使用
ExecuteNonQuery 方法的重载,它通过 ADO.NET 事务支持来构建。
也可以在存储过程中使用 Transact-SQL 语句直接控制手工事务。例如,可以使用单一存储过程来执行事务操作,存储过程使用了 Transact-SQL 语句如
BEGIN TRANSACTION、
END TRANSACTION、和
ROLLBACK TRANSACTION。
另一种方法是使用自动(COM+)事务。自动事务简化了程序模型,因为它们不需要显式的启动新的事务、提交它或者放弃它。而是在运行时,添加声明性属性到 .NET 类上,以指定对象的事务需求。这允许你简单的配置多个组件在同一事务中工作,而且非常适用于必须跨越多个远程数据库的事务。
COM+ 事务特别适用于跨越多个远程数据库的事务。然而,它们也导致了附加的运行时开销,因此在考虑如何很好的要求应用程序执行时必须得小心。
关于事务的更多信息和选择适当的模型的指南,请参见
.NET Data Access Architecture Guide。
快速入门关于如何在一个事务中完成多个更新的扩展示例,请参见快速入门漫游,漫游:在一个事务中完成多个更新。
在一个事务中使用 ExecuteNonQuery 下列代码展示了如何在一个事务中使用多个
ExecuteNonQuery 。
C#
public bool Transfer(int transactionAmount, int sourceAccount, int destinationAccount)
{
bool result = false;
// Create the database object, using the default database service. The
// default database service is determined through configuration.
Database db = DatabaseFactory.CreateDatabase();
// Two operations, one to credit an account, and one to debit another
// account.
string sqlCommand = "CreditAccount";
DbCommand creditCommand = db.GetStoredProcCommand(sqlCommand);
db.AddInParameter(creditCommand, "AccountID", DbType.Int32, sourceAccount);
db.AddInParameter(creditCommand, "Amount", DbType.Int32, transactionAmount);
sqlCommand = "DebitAccount";
DbCommand debitCommand = db.GetStoredProcCommand(sqlCommand);
db.AddInParameter(debitCommand, "AccountID", DbType.Int32, destinationAccount);
db.AddInParameter(debitCommand, "Amount", DbType.Int32, transactionAmount);
using (DbConnection connection = db.CreateConnection())
{
connection.Open();
DbTransaction transaction = connection.BeginTransaction();
try
{
// Credit the first account.
db.ExecuteNonQuery(creditCommand, transaction);
// Debit the second account.
db.ExecuteNonQuery(debitCommand, transaction);
// Commit the transaction.
transaction.Commit();
result = true;
}
catch
{
// Roll back the transaction.
transaction.Rollback();
}
connection.Close();
return result;
}
}
Visual Basic
Public Function Transfer(ByRef transactionAmount As Integer, ByRef sourceAccount As Integer, ByRef destinationAccount As Integer) As Boolean
Dim result As Boolean = False
' Create the database object, using the default database service. The
' default database service is determined through configuration.
Dim db As Database = DatabaseFactory.CreateDatabase()
' Two operations, one to credit an account, and one to debit another
' account.
Dim sqlCommand As String = "CreditAccount"
Dim creditCommand As DbCommand = db.GetStoredProcCommand(sqlCommand)
db.AddInParameter(creditCommand, "AccountID", DbType.Int32, sourceAccount)
db.AddInParameter(creditCommand, "Amount", DbType.Int32, transactionAmount)
sqlCommand = "DebitAccount"
Dim debitCommand As DbCommand = db.GetStoredProcCommand(sqlCommand)
db.AddInParameter(debitCommand, "AccountID", DbType.Int32, destinationAccount)
db.AddInParameter(debitCommand, "Amount", DbType.Int32, transactionAmount)
Using connection As DbConnection = db.CreateConnection()
connection.Open()
Dim transaction As DbTransaction = connection.BeginTransaction()
Try
' Credit the first account.
db.ExecuteNonQuery(creditCommand, transaction)
' Debit the second account.
db.ExecuteNonQuery(debitCommand, transaction)
' Commit the transaction.
transaction.Commit()
result = True
Catch
' Roll back the transaction.
transaction.Rollback()
End Try
connection.Close()
Return result
End Using
End Function
顶部编辑4.6 - 使用 DataSet 更新数据库
数据库必须定期的用新的信息更新。例如,在基于 Web 的在线零售应用程序中,可能要添加新的客户到数据库中,修改与一个客户 ID 相关的姓名,或者删除完整的客户记录。
典型目标在此场景中的目标是传输存储在
DataSet 对象中的数据到数据库中。(要记住的是,
DataSet 是信息的本地缓存;修改不会自动的传播回原始数据源。)
解决方案要传播
DataSet 对象中的修改到数据库中,请使用
UpdateDataSet 方法。
快速入门关于如何使用 DataSet 对象更新数据库的扩展示例,请参见快速入门漫游,漫游:使用
DataSet 更新数据库。
使用 UpdateDataSet下列代码展示了如何使用
UpdateDataSet 方法。
C#
Database db = DatabaseFactory.CreateDatabase();
DataSet productsDataSet = new DataSet();
string sqlCommand = "Select Select ProductID, ProductName, CategoryID, UnitPrice, LastUpdate From Products";
DbCommand dbCommand = db.GetSqlStringCommand(sqlCommand);
string productsTable = "Products";
// Retrieve the initial data.
db.LoadDataSet(dbCommand, productsDataSet, productsTable);
// Get the table that will be modified.
DataTable table = productsDataSet.Tables[productsTable];
// Add a new product to existing DataSet.
DataRow addedRow = table.Rows.Add(new object[] {DBNull.Value, "New product", 11, 25});
// Modify an existing product.
table.Rows[0]["ProductName"] = "Modified product";
// Establish the Insert, Delete, and Update commands.
DbCommand insertCommand = db.GetStoredProcCommand("AddProduct");
db.AddInParameter(insertCommand, "ProductName", DbType.String, "ProductName", DataRowVersion.Current);
db.AddInParameter(insertCommand, "CategoryID", DbType.Int32, "CategoryID", DataRowVersion.Current);
db.AddInParameter(insertCommand, "UnitPrice", DbType.Currency, "UnitPrice", DataRowVersion.Current);
DbCommand deleteCommand = db.GetStoredProcCommand("DeleteProduct");
db.AddInParameter(deleteCommand , "ProductID", DbType.Int32, "ProductID", DataRowVersion.Current);
DbCommand updateCommand = db.GetStoredProcCommand("UpdateProduct");
db.AddInParameter(updateCommand, "ProductID", DbType.Int32, "ProductID", DataRowVersion.Current);
db.AddInParameter(updateCommand, "ProductName", DbType.String, "ProductName", DataRowVersion.Current);
db.AddInParameter(updateCommand, "LastUpdate", DbType.DateTime, "LastUpdate", DataRowVersion.Current);
// Submit the DataSet, capturing the number of rows that were affected.
int rowsAffected = db.UpdateDataSet(productsDataSet, "Products", insertCommand, updateCommand, deleteCommand, Microsoft.Practices.EnterpriseLibrary.Data.UpdateBehavior.Standard);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim productsDataSet As DataSet = new DataSet()
Dim sqlCommand As String = "Select Select ProductID, ProductName, CategoryID, UnitPrice, LastUpdate From Products"
Dim dbCommand As DbCommand = db.GetSqlStringCommand(sqlCommand)
Dim productsTable As String = "Products"
' Retrieve the initial data.
db.LoadDataSet(dbCommand, productsDataSet, productsTable)
' Get the table that will be modified.
Dim table As DataTable = productsDataSet.Tables(productsTable)
' Add a new product to existing DataSet.
Dim addedRow As DataRow = table.Rows.Add(New Object() {DBNull.Value, "New product", 11, 25})
' Modify an existing product.
table.Rows(0)("ProductName") = "Modified product"
' Establish the Insert, Delete, and Update commands.
Dim insertCommand As DbCommand = db.GetStoredProcCommand("AddProduct")
db.AddInParameter(insertCommand, "ProductName", DbType.String, "ProductName", DataRowVersion.Current)
db.AddInParameter(insertCommand, "CategoryID", DbType.Int32, "CategoryID", DataRowVersion.Current)
db.AddInParameter(insertCommand, "UnitPrice", DbType.Currency, "UnitPrice", DataRowVersion.Current)
Dim deleteCommand As DbCommand = db.GetStoredProcCommand("DeleteProduct")
db.AddInParameter(deleteCommand, "@ProductID", DbType.Int32, "ProductID", DataRowVersion.Current)
Dim updateCommand As DbCommand = db.GetStoredProcCommand("UpdateProduct")
db.AddInParameter(updateCommand, "ProductID", DbType.Int32, "ProductID", DataRowVersion.Current)
db.AddInParameter(updateCommand, "ProductName", DbType.String, "ProductName", DataRowVersion.Current)
db.AddInParameter(updateCommand, "LastUpdate", DbType.DateTime, "LastUpdate", DataRowVersion.Current)
' Submit the DataSet, capturing the number of rows that were affected.
Dim rowsAffected As Integer = db.UpdateDataSet(productsDataSet, "Products", insertCommand, updateCommand, _
deleteCommand, Microsoft.Practices.EnterpriseLibrary.Data.UpdateBehavior.Standard)
使用提示UpdateDataSet 方法可以指定在遇到错误时将发生的行为。此行为必须受
Database 对象的子类支持。前面的示例使用了
UpdateBehavior.Standard ,它指出了如果错误发生时,更新将停在错误点上。没有其他的行受影响,已修改的行也不会发生回滚。其他的更新行为如下:
- Standard。这不会干涉 DataAdapter 对象的 Update 命令。如果 Update 命令遇到一个错误,更新将停止。Datatable 中的其他行将不会受影响。
- Continue。DataAdapter 对象的 Update 命令遇到一个错误,更新将继续。Update 命令将尝试更新余下的行。
- Transactional。如何 DataAdapter 对象遇到一个错误,所有更新的行将回滚。
顶部编辑4.7 - 获取多行 XML 数据
可能要使用 XML 数据的一个例子是,在电子商务应用程序中,它允许客户请求 XML 格式的产品分类。
典型目标在此场景中,目标是从 SQL Server 数据库中获取 XML 格式的数据。你必须提供一个查询数据库以获取产品分类信息的方法,然后返回给调用者的为
XmlReader 。
解决方案SQL Server 2000 以更新的版本提供了 XML 支持并允许从数据库中获取 XML 数据。例如,可能使用
FOR XML 子句从数据库中获取 XML 片段(那就是,没有根元素的 XML 文档)。
SqlDatabase 类提供了
ExecuteXmlReader 方法。此方法返回提供仅能前向访问 XML 数据流的
XmlReader 对象,它假定执行的命令包括一条包含有效的
FOR XML 子名的 Transact-SQL 语句。
快速入门对于如何使用
ExecuteXmlReader 方法获取多行数据为 XML 的扩展示例,请参见快速入门漫游,漫游:获取多行数据为 XML 。
使用 ExecuteXmlReader下列代码展示了如何使用
ExecuteXmlReader 方法。
C#
SqlDatabase dbSQL = DatabaseFactory.CreateDatabase("EntLibQuickStartsSql") as SqlDatabase;
// Use "FOR XML AUTO" to have SQL return XML data.
string sqlCommand = "SELECT ProductID, ProductName FROM Products FOR XML AUTO";
DbCommand dbCommand = dbSQL.GetSqlStringCommand(sqlCommand);
XmlReader productsReader = null;
StringBuilder productList = new StringBuilder();
try
{
productsReader = dbSQL.ExecuteXmlReader(dbCommand);
// Iterate through the XmlReader and put the data into our results.
while (!productsReader.EOF)
{
if (productsReader.IsStartElement())
{
productList.Append(productsReader.ReadOuterXml());
productList.Append(Environment.NewLine);
}
}
}
finally
{
// Close the Reader.
if (productsReader != null)
{
productsReader.Close();
}
}
Visual Basic
Dim dbSQL As SqlDatabase = DirectCast(DatabaseFactory.CreateDatabase("EntLibQuickStartsSql"), SqlDatabase)
' Use "FOR XML AUTO" to have SQL return XML data.
Dim sqlCommand As String = "SELECT ProductID, ProductName FROM Products FOR XML AUTO"
Dim dbCommand As DbCommand = dbSQL.GetSqlStringCommand(sqlCommand)
Dim productsReader As XmlReader = Nothing
Dim productList As StringBuilder = New StringBuilder()
Try
productsReader = dbSQL.ExecuteXmlReader(dbCommand)
' Iterate through the XmlReader and put the data into our results.
While (Not productsReader.EOF)
If (productsReader.IsStartElement()) Then
productList.Append(productsReader.ReadOuterXml())
productList.Append(Environment.NewLine)
End If
End While
Finally
' Close the Reader if there is no active transaction.
If (Not productsReader Is Nothing And Transaction.Current Is Nothing) Then
productsReader.Close()
End If
End Try
使用提示在从
XmlReader 中读取数据时,连接必须保持打开。
SqlCommand 对象的
ExecuteXmlReader 方法现在不支持
CommandBehavior.CloseConnection 枚举值,所以在完成对读取器的使用且如果不有活动的事务时必须显式的关闭连接。如果
Transaction.Current 静态属性不为 null ,它意味着
TransactionScope 实例是活动的。在这种情况下,不能关闭连接,因为应用程序块为数据库命令使用了共享连接。
另一种选择是,可以调用
ExecuteDataSet 方法之一来获取
DataSet 对象,因为此对象允许将数据视为 XML 来进行操作和访问。然而,使用 SQL Server 的 XML 支持和
FOR XML 子句提供了极大的灵活性,因为它允许决定元素的名称,是否使用严格的元素或属性模式,模式是否将和 XML 数据一起返回,等等。这也意味着避免了与创建
DataSet 或缓存数据相关的性能影响。
顶部编辑5 - 数据访问应用程序块的设计
数据访问应用程序块包含下列特性:
- 用不同数据库系统工作的简单而有效的方法(请参见用于简单数据访问的设计)
- 开发数据库诊断应用程序的方法(请参见用于数据库诊断应用程序的设计)
- 调整和验证数据库配置设置的简单方法
设计目标应用程序块被设计以达到下列目标:
- 封装用于完成绝大多数普通数据访问任务的逻辑。
- 消除常见代码错误,如错误的关闭连接。
- 减轻开发人员为常见数据访问任务编写重复代码的需要。
- 减少定制代码的需要。
- 为数据访问加入最佳实践,就像在.NET Data Access Architecture Guide中描述的一样。
- 确保应用程序块的功能尽可能的可以与不同的数据库类型一起工作。
- 确保为一种数据库编写的应用程序,在数据访问方面,也同样是为另一种数据库类型编写的应用程序。
设计亮点图 1 说明了数据访问应用程序块中关键类之间的关系。
 图 1 数据访问应用程序块中关键类之间的关系 |
假设客户代码使用的是应用程序块配置信息,它调用了
DatabaseFactory 类的
CreateDatabase 方法创建
Database 对象的实例。
DatabaseFactory 使用在配置文件中找到的配置信息来决定要构造的
Database 对象类型,然后返回给应用程序。工厂使用下列标准来决定要构造的
Database 对象类型:
- 如果客户代码传递在配置文件中的标识连接字符串的数据库实例名称,工厂将使用那个字符串来创建 Database 对象。
注意:.NET Framework 类现在维护了在配置文件中的 connectionStrings 节中的连接字符串。这意味着连接字符串的定义可以被所有访问 connectionStrings 节的应用程序所共享。
- 如果客户代码不传递数据库实例名称,工厂将由配置文件中的默认实例设置标识连接字符串。在 dataConfiguration 配置节中的 defaultDatabase 属性控件了默认实例。下列来自配置文件的 XML 片段展示了与 Production 连接字符串对应的默认实例。
<dataConfiguration defaultDatabase="Production">
在配置文件中的 <connectionStrings> 节映射逻辑标识(名称)到连接字符串和
DbProviderFactory 类型。
DatabaseFactory 从连接字符中获得 ADO.NET 数据提供程序的逻辑名称。
DatabaseFactory 对象然后调用 .NET Framework 的
DbProviderFactory 类获得 ADO.NET 数据提供程序的完整类型名称。
DatabaseFactory 使用此信息决定构造的
Database 对象的类型。尽管在连接字符串中的 .NET Framework 认为数据库提供程序名称是可选的,但数据访问应用程序块必须需要它们。
应用程序块配置代码包含了默认的映射。映射了
SqlDatabase 对象到
System.Data.SqlClient 数据提供程序,映射了
OracleDatabase 对象到
System.Data.OracleClient 数据提供程序,
GenericDatabase 对象到所有其他的数据提供程序。可以使用配置控制台覆写默认的映射。
修改默认的映射或者使用配置控制台添加新的映射会在配置文件中创建一个
providerMappings 节。
providerMappings 节映射
DbProviderFactory 类型到
Database 类型。可以通过创建自己的数据库类并映射它到
DbProviderFactory 对象来添加自己的映射。
抽象基类
Database 定义了通用接口并为数据访问方法提供了许多实现。
SqlDatabase 类和
OracleDatabase 类派生自
Database 类。它们提供了到各自数据库服务器系统的方法,包括了通用功能的不同数据库的不同实现,以及专用于相应数据库系统的功能。应用程序块还包括了一个
GenericDatabase 类。此类不提供任何数据专用的特性,并可以与任何 ADO.NET 数据提供程序一起进行操作。
应用程序块支持存储过程的参数动态发现。这种发现需要到数据库系统的一次回返。
ParameterCache 类允许缓存参数信息,这避免了在同样存储过程的后继调用中的回返。(
GenericDatabase 类不支持参数发现)。
顶部编辑5.1 - 用于简化数据访问的设计
开发人员在构建数据访问解决方案时会面临许多实现选择和需求。他们必须以不同的方式访问数据,解决方案必须工作在不同的数据库类型下,并以不同的方式处理数据。结果是,开发人员可能发现它们自己在重复完成通用任务的代码,例如管理连接和给命令中的参数赋值。
另一个挑战是在如何实现数据访问操作中维持一致的方法。可能是在单个项目、多个项目或者企业级解决方案中维持这种一致性。数据访问的制度化的方法使代码更易于理解、更加可以预测并易于维护。
数据访问应用程序块通过封装完成常见数据库操作的逻辑来简化数据访问。这些方法还处理如打开和关闭连接这样的通用内部任务。它们是数据库透明的,这意味着它们用 SQL Server 和 Oracle 数据库工作时不需要进行修改。为一种数据库编写的应用程序所使用的方法与为另一种数据库所编写的方法是一样的。这意味着应用程序在访问数据的方式方面是一致的。另外,
GenericDatabase 类支持许多通用于 ADO.NET 数据提供程序间的一样的特性。
设计内涵应用程序块为简化访问数据的任务而设计。因此,它需要下列的设计决定:
- 仅暴露了少量的开发人员需要理解的方法。
- 封装了常见的内部任务。
- 使其易于处理参数。
- 提供了很好的性能。
- 简化数据库对象的创建。
下面的节描述了这些决定。
有限的操作集应用程序块支持少量的简化最常见数据访问任务的操作。它提供了抽象的基类:
Database,Database 类定义了应用程序块支持的方法集合。这些方法如下:
- ExecuteDataSet
- LoadDataSet
- ExecuteReader
- ExecuteScalar
- ExecuteNonQuery
- UpdateDataSet
每个方法都有多个重载。重载通过每个方法传递的信息进行不同程度的控制,以方便不同的程序类型。重载类之一允许传递一个 ADO.NET 的类型
DbCommand 的对象,如下面示例所示:
C#
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductList");
DataSet productsDataSet = db.ExecuteDataSet(dbCommand);
Visual Basic
Dim db As Database = DatabaseFactory.CreateDatabase()
Dim dbCommand As DbCommand = db.GetStoredProcCommand("GetProductList")
Dim productsDataSet As DataSet = db.ExecuteDataSet(dbCommand)
在
Database 类上的每个可用方法,有二个接受
DbCommand