Enterprise Library 4 数据访问应用程序块

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 数据库一起工作。数据访问应用程序块为解决开发人员在编写数据库应用程序时所面对的绝大多数普通任务而设计。这些任务根据场景进行了组织。每个场景都人出了一个真实世界条件下的示例,例如从分类中获取信息或者未完成银行事务,描述了条件所要求的数据库功能,并展示完成任务的代码。

根据场景组织这些任务的目的是给代码一些上下文,来替代展示一组孤立的方法,而没有它们最好使用在哪儿的意义,场景为代码提供了一种设置,将它放置在其应用程序必须访问数据库的许多开发人员所熟悉的条件中。

场景如下:

  • 使用 DataReader 获取多行数据
  • 使用 DataSet 获取多行数据
  • 运行一个命令并获取输出参数
  • 运行一个命令并获取单值项
  • 在一个事务中执行多个操作
  • 从 SQL Server 中获取 XML 数据
  • 使用包含在 DataSet 对象中的数据更新数据库

示例应用程序代码 下列代码展示了如何调用一个存储过程并返回一个 DataSet

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);  

从3.1版本之后修改的特性

一般情况下,使用数据访问应用程序块的早期版本发布构建的应用程序不需要修改任何代码就能使用 May 2008 发行的功能。可能需要更新引用以指向新的程序集,并更新配置文件以引用正确的应用程序版本。然而在version 3.1 (May 2007)所做的一些修改会影响到你从早期版本迁移到现在的版.下面描述这些改变:

.NET Framework 2.0 TransactionScope 类

已经修改某些 Database 类的方法以使用 .NET Framework 2.0 的 TransactionScope 类。这些方法,例如 ExecuteNonQuery ,已通过用 GetOpenConnection 方法替换掉 GetConnection 方法来修改为识别 TransactionScop 实例的有效时机。如果编写了一个继承自 Database 类的类,将需要考虑这些变化来重写代码。如果继续使用 GetConnection 方法,将会收到一个编译错误。 另外,如果应用程序使用了 ExecuteXmlReader 方法,可能需要重写代码以测试查看在关闭连接前 TransactionScope 实例是否是有效的。

SQL Server Compact Edition

Enterprise Library 3.1 – May 2007 和之后版本支持 SQL Server Compact Edition (CE)。SQL Server CE 提供了精减的关系数据库的特性,以用于桌面和移动应用程序,这些程序需要本地数据库存储但又不需要完整的 SQL Server 的功能。

何时使用数据访问应用程序块

数据访问应用程序块包含少量简化绝大多数访问数据库的普通方法的方法。每个方法都封装了获取数据所需要的逻辑以及管理数据库连接。如果应用程序中使用标准的数据访问技术就可以考虑使用应用程序块。 应用程序块补充了 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 可能更适合。

使用数据访问应用程序块开发应用程序

首先解释了如何配置应用程序块并将它添加到应用程序中。然后,在关键场景中,解释了如何在特定场景中使用应用程序块,例如获取单个项或者使用 DataSet 对象获取多行。最后,在开发任何细节中,给出了关于如连接管理、参数处理和处理异常等方面的更多信息。本主题假设使用的是原始的应用程序块,即没有扩展的。要学习如何添加功能,请参见扩展和修改数据访问应用程序块。

输入配置信息

下面这些过程展示了如何配置数据访问应用程序块。

此过程解释了如何配置数据访问应用程序块。与节点关联的属性显示在右边的面板里。

添加数据访问应用程序块

  1. 打开配置文件。更多信息,请参数配置应用程序块。
  2. 右单击Application Configuration,指向 New ,然后单击 Data Access Application Block

下一过程解释了如何配置默认的数据库实例,此实例在应用程序调用不指定实例名称的 DatabaseFactory.CreateDatabase 方法时使用。

配置默认数据库

  1. 在右面板中,展开 DefaultDatabase 属性。
  2. DefaultDatabase 属性输入连接字符串的名称或者从下拉列表中选择它。默认的连接字符串名称是 ConnectionString 。
  3. (可选的)输入一个新的名称来设计 Name 属性,默认名称是 ConnectionString
  4. ProviderName 属性节,如果愿意,可能修改提供程序的名称。输入提供程序的名称或者从下拉列表中选择它。提供程序的默认名称是 System.Data.SqlClientProviderName 属性必须是一个在 DBProviderFactory 类中指定的提供程序的名称。

下一过程解释了如何为命名数据库实例创建连接字符串。当配置控制台保存连接字符串时,它生成了一个以 name = value 格式保存值对的以分号分割的字符串。例如,如果使用配置控制台来用默认值生成连接字符串,配置控制台台将生成下列连接字符串。

Database=Database;Server=(local)\SQLEXPRESS;Integrated Security=SSPI;

配置连接字符串

  1. 单击 ConnectionString 节点。
  2. (可选的)输入一个新的名称以设置 Name 属性,这是 ConnectionString 节点的名称。默认的名称是 ConnectionString
  3. (可选的)在 ProviderName 属性节,修改提供程序的名称。输入提供程序的名称或者从下拉列表中选择它。默认的提供程序的名称是 System.Data.SqlClientProviderName 属性必须是一个指定在 DbProviderFactory 类中的提供程序的名称。
  4. 用下列值更新 ConnectionString 属性。例如:
Database=Database;Server=(local)\SQLEXPRESS;Integrated Security=SSPI

下一过程展示了如何配置一个 SQL Server CE 数据库。如果应用程序总是使用在配置期间命名的单一文件,这些步骤是合适的。关于 SQL Server CE 的更多信息,请参见创建数据库对象的细节。

配置 SQL Server CE

  1. 右单击 Custom Provider Mappings,指向 New ,然后单击 Provider Mapping
  2. 属性面板中单击Nmae,在下拉框中选择Microsoft.SqlServerCe.Client
  3. 属性面板中单击 TypeName 属性。单击省略号(...)按钮
  4. Type Selector 中,找到并双击 SqlCeDatabase
  5. ConnectionStrings 节点上右单击并单击 New ,然后单击 Connection String
  6. (可选的)输入新的名称以设置 Name 属性。这是 ConnectionString 节点的名称。默认的名称是 ConnectionString
  7. ProviderName 属性节,修改提供程序的名称为 Microsoft.SqlServerCe.Client
  8. ConnectionString 属性节,输入理想的 SQL Server CE 连接字符串,例如:
Data Source='C:\MyApp\MyDatabase.sdf'

下一过程展示了如果添加 Oracle 包。Oracel 包服务是分组存储过程到普通组的一种方式,通常基于它们的功能。当应用程序调用在包中的 Oracle 存储过程时,代码必须用包名做为存储过程的前缀。例如,要调用在命名为 Employee_pkg 的包中的名为 GetEmployeeName 的过程,将调用 Employee_pkg.GetEmployeeName

将这段代码加入到应用程序中将降低可移植性,因为语法专用于 Oracle 。另一种替换做法是,数据访问应用程序块会用包名做为存储过程的前缀。这意味着客户端代码在调用存储过程时不需要指定包名。要做到这一点,应用程序块使用在配置文件中的信息。OraclePackage 节点保存了一个名称/前缀对。名称是包的名称,前缀是一个与包相关的字符串。所有以指定的前缀开始的存储过程都假定在相关的包内。

当应用程序调用一个存储过程时,数据访问应用程序块检查看是否以配置文件中的某个前缀开始。如果是,应用程序块为存储过程加上相应的包名前缀。(应用程序块将使用找到的第一个匹配)。如果指定一个星号(*)为前缀,关联包将用于所有存储过程。

配置 Oracle 包

  1. 右单击 ConnectionString ,指向 New ,然后单击 OraclePackages
  2. 单击 OraclePackage
  3. 输入 Oracle 包的名称以修改 Name 属性。默认为 OraclePackage
  4. 输入 Prefix 属性的值。

下一过程解释了如何通过关联提供程序和数据库全名称来添加自定义的提供程序映射。 配置自定义的提供程序

  1. 右单击 CustomProviderMappings 节点,指向 New ,然后单击 ProviderMapping
  2. (可选的)输入新的名称以设置 Name 属性。输入提供程序的名称或从下拉列表中选择。默认的提供程序是 System.Data.SqlClientProviderName 属性必须是一个在 DbProviderFactory 类中指定的提供程序名称。
  3. TypeName 属性节中。单击省略号按钮(...)并使用'Type Selector 选择 Enterprise Library 数据库类型的全名称。

可以手工编辑 XML 数据,但 Enterprise Library 极大的简化了此任务。如果选择手工编辑 XML ,则要使用包含在本主题中的模式信息。 配置文件有如下的节处理程序声明:

<configSections>
  <section name="dataConfiguration"
           type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings,
                 Microsoft.Practices.EnterpriseLibrary.Data, Version=4.0.0.0,
                 Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  <section name="oracleConnectionSettings" 
           type="Microsoft.Practices.EnterpriseLibrary.Data.Oracle.Configuration.OracleConnectionSettings,
                 Microsoft.Practices.EnterpriseLibrary.Data, Version=4.0.0.0,
                 Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>

节处理程序声明包括配置设置节的名称和处理节中配置数据的节处理程序的类名。第一个配置设计节的名称为 dataConfiguration ,节处理程序的类名为 DatabaseSettings (在 Microsoft.Practices.EnterpriseLibrary.Data.Configuration 命名空间中)。 第二个配置设置节是 oracleConnectionSettings 。节处理程序类的名称为 OracleConnectionSettings (在 Microsoft.Practices.EnterpriseLibrary.Data.Oracle.Configuration 命名空间中 )。 connectionStrings 元素 connectionStrings 元素列出了可被应用程序使用的数据库连接,此元素不是必须的。 属性和子元素 下面的节描述了 connectionStrings 元素的属性和子元素。 add 子元素 add 元素是 connectionStrings 元素的子元素。add 元素添加一个数据库连接,此元素不是必须的,可以有多个 add 元素。

属性

描述

name

由应用程序访问的数据库实例的逻辑名称。在节中,名称必须是唯一的。此属性是必须的。

providerName

提供程序的名称。默认情况下,提供程序的名称定义在 Machine.config 文件中。providerName 名称必须是一个在 DBProviderFactory 类中指定的提供程序的名称。此属性是必须的。

connectionString

可用于被选的提供程序的连接字符串,此属性是必须的。

dataConfiguration 元素 元素仅在需要指定一个默认数据库或者自定义的提供程序的映射时有用。 表 2 列出了 dataConfiguration 元素的属性。

属性

描述

defaultDatabase

连接字符串实例的名称,此实例仅用于应用程序调用不带实例名的 DatabaseFactory.CreateDatabase 方法。

providerMappings 子元素 这是一个 dataConfiguration 元素的子元素,只有在通过派生自 ADO.NET 的 Database 类而不是 GenericDatabase 类的提供程序时才需要指定提供程序的映射。SQL Server 和 Oracle 数据库默认已配置,所以不需要再在此节中指定。指定在此节中的一个数据库示例是 SQL Server CE 。 add 子元素 addproviderMappings 元素的子元素。add 元素添加一个数据库连接。此元素不是必须的,可以有多个 add 元素。

表3 列出了素的属性

属性

描述

databaseType

派生自 Database 类的类型名,此属性是必须的。

name

使用的 ADO.NET 提供程序类型名称。名称必须在 DBProviderFactory 类中指定。此属性是必须的。

oracleConnectionSettings

只有在需要指定 Oracle 数据库包映射时才需要此元素。

add 子元素 add 元素是 oracleConnectionSettings 元素的子元素。add 元素添加一个 Oracle 连接字符串实例。此元素不是必须的。可以有多个 add 元素。 属性 表 4 列出了 add 元素的属性

属性

描述

name

Oracle 连接字符串实例的名称。此属性是必须的。

packages 子元素 这是 add 元素的一个子元素,指定一个 Oracle 的包。此元素是必须的。 add 子元素 这是 packages 元素的一个子元素。 add 元素添加一个 Oracle 的包。此元素不是必须的。可以有多个 add 元素。 属性 表 5 列出了 add 子元素的属性。

属性

描述

Name

Oracle 包的名称。此属性是必须的。

Prefix

Oracle 包的前缀。此属性是必须的。

添加应用程序代码

数据访问应用程序块为支持绝大多数访问数据库场景而设计。在添加自己的应用程序代码时,请参考在关键场景节中的场景,然后选择一种与自己的情况最匹配的方法。使用场景中的代码,或者如果需要,修改它以适合自己的需要。

首先,必须准备自己的应用程序以使用数据访问应用程序块。

准备应用程序

  1. 添加到数据访问应用程序块程序集的引用。在 Visual Studio 中,在解决方案管理器中右单击项目节点,然后单击添加引用。单击浏览标签,然后导航到 Microsoft.Practices.EnterpriseLibrary.Data.dll 的位置。选择程序集,然后单击确定以添加引用。
  2. 按同样的步骤,添加到 Enterprise Library 内核程序集 Microsoft.Practices.EnterpriseLibrary.Common.dllMicrosoft.Practices.ObjectBuilde2r.dll 的引用。
  3. (可选的) 要不带完整的精确的元素引用使用来自加密应用程序块的元素,可以添加下列的 using 语句(C#)或者 Imports 语句(Visual Basic)到源代码文件的顶部。
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data;

创建 Database 对象 所有数据访问方法都相对于 Database 的对象。可以使用 DatabaseFactory 来创建 Database 对象。由工厂生成的 Database 对象的特定类型是由应用程序的配置信息决定的。 可以使用配置控制台指定一个默认的数据库实例。在不传递数据库实例名调用 CreateDatabase 方法时,DatabaseFactory 创建由默认实例指定的 database 。下列应用程序代码展示了如何创建一个默认实例的 Database 对象。

Database db = DatabaseFactory.CreateDatabase();

另一种方法是,应用程序代码可以指定一个命名的数据库实例。例如,如果使用配置控制台创建了名为 "Sales" 的实例,则为特定实例创建 Database 对象的代码可能如下。

Database db = DatabaseFactory.CreateDatabase("Sales");

如果要创建的数据库的连接字符串是已知的,也要以放弃应用程序的配置信息,而是使用构造函数直接创建 Database 对象。因为 Database 类是一个抽象基类,所以必须构建一个它的派生类型。派生的 Database 类型决定了 ADO.NET 数据提供程序。例如,SqlDatabase 类使用 SqlClientFactory 提供程序,SqlCeDatabase 使用 SqlCeProviderFactory 提供程序,以及 OracleDatabase 使用 OracleClientFactory 提供程序。为连接字符串构建正确类型的 Database 类是你的责任。 下列代码使用提供的连接字符串创建了一个 SqlDatabase 对象。

// Assume the method GetConnectionString exists in your application and 
// returns a valid connection string.
string myConnectionString = GetConnectionString();

SqlDatabase sqlDatabase = new SqlDatabase(myConnectionString);

如果通过不是 ADO.NET SQL 数据提供程序或者 Oracle 数据提供程序的数据提供程序使用一个连接字符串,可以创建一个 GenericDatabase 对象。在创建 GenericDatabase 对象时,必须支持 DbProviderFactory 对象。 选择适当的重载方法 每个数据访问方法都有多个重载。下列描述和指南可以帮助你选择合适的重载:

  • 有可以接受 ADO.NET DbCommand 对象的重载。这些重载为每个方法提供了最大的灵活性和控制。
  • 有接受存储过程名称和用于存储过程参数值的值集合的重载。这些重载在应用程序调用仅有输入参数的存储过程时比较方便。
  • 有接受 System.Data.CommandType 和表示命令的字符串的重载。这些方便的重载在应用程序执行不带参数的内联 SQL 语句或存储过程时使用。
  • 最后,以上每个重载都包含一个接受一个事务的重载。这允许在一个已存在的事务中执行方法时使用需要的重载类型。

每个关键场景示范了特定方法可用重载之一,许多场景都可以使用其他可用的重载完成。

创建 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 对象。

Database dbSvc = DatabaseFactory.CreateDatabase(); 

注意 如果配置文件没有指定默认实例,并且客户代码在调用 CreateDatabase 方法时也没有指定参数,应用程序块将抛出异常。

使用实例 要使用实例,可以通过逻辑名称在应用程序代码中引用 database ,并且修改数据库配置信息(如位置或连接字符串信息)而不用重新编译代码。 下列示例展示了如何使用名称“Sales”创建 database 。

// Use a named database instance that refers to an arbitrary database type, 
// which is determined by configuration information.
Database myDb = DatabaseFactory.CreateDatabase("Sales");

创建特定的 Database 类型 如果必须使用专用于特定数据库类型的方法,在创建数据库时可以指定 database 类型。下列代码要求工厂创建一个 SqlDatabase 对象。

// Create a SQL database.
SqlDatabase dbSQL = DatabaseFactory.CreateDatabase("Sales") as SqlDatabase;

同样,要创建一个 Oracle 数据库,使用 OracleDatabase 。要创建一个 SQL CE 数据库,使用 SqlCeDatabase 类型。 不使用配置创建一个数据库 可以通过提供一个连接字符串给 database 类构造函数来不使用配置数据的创建一个数据库对象。下列代码展示了如何创建一个 SqlDatabase 对象。

// Assume your application contains the routine GetConnectionString. 
string myConnectionString = GetConnectionString();

SqlDatabase sqlDatabase = new SqlDatabase(myConnectionString);

下列代码展示了如何创建一个 GenericDatabase 对象。必须提供连接字符串和 DbProviderFactory 对象。在这种情况下,DbProviderFactory 对象是 OdbcFactory 。

GenericDatabase db = new GenericDatabase(connectionString, OdbcFactory.Instance); 

使用 SQL Server CE SQL 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 方法,如 ExecuteScalarExecuteNonQuery ,以一个存储过程做为参数的话,应用程序块将抛出异常。不用存储过程,可以使用内联的 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 站点上的 Oracle Services for MTS以获得适当的下载。 使用提示 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 对象类型之间的映射。更多信息,请参见数据访问应用程序块的设计。

创建 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

Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "Select CustomerID, LastName, FirstName From Customers";
DbCommand dbCommand = db.GetSqlStringCommand(sqlCommand);

用于存储过程的 DbCommand 对象

要执行存储过程,必须使用 GetStoredProcCommand 方法来创建 DbCommand 对象。要执行存储过程的名称在方法调用时做为一个参数传递。

下列代码展示了如何使用 GetStoredProcCommand

Database db = DatabaseFactory.CreateDatabase();

DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory"); 

注意:存储过程的参数受 Database 类的方法的支持。关于如何使用存储过程参数参数的更多信息,请参见处理参数。

管理连接

数据库连接是有限资源,它们的妥善管理对可扩展的应用程序来说是必不可少的。仅在需要时保持连接打开并尽快关闭是一个很好的实践。根据设计,绝大多数的 Database 类方法在每次调用时打开和关闭到数据库的连接。因为,应用程序代码不需要包含用于管理连接的代码。(默认情况下,基于性能的原因,ADO.NET 将连接返回到连接池中,而不是关闭他们。因此,不需要缓存 Database 对象。)

例如,ExecuteDataSet 返回包含所有数据的 DataSet 对象。这给了你一个自己的本地副本。对 ExecuteDataSet 的调用打开了一个连接、组装了一个 DataSet、然后在返回结果前关闭连接。

下列代码示范了 ExecuteDataSet 方法的使用。

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);

然而,在关闭连接时有一些未清理的其他情况。一个例子就是 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 对象。

Database db = DatabaseFactory.CreateDatabase();

DbCommand dbCommand = db.GetSqlStringCommand("Select Name, Address From Customers");
using (IDataReader dataReader = db.ExecuteReader(dbCommand))
{
// Process results
} 

使用 TransactionScope 类

在此对 Database 类的某些方法进行了一些修改,以利用 .NET Framework 2.0 的 TransactionScope 类。此类自动将数据库调用加入到一个外围的事务中。这在将业务对象加入到一个事务中而不传递事务到这些业务对象中时非常有用。以下是 TransactionScope 类的使用的基本模型。

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.”

创建可移植的数据库应用程序

如果应用程序必须工作在多个数据库类型下,有些问题就必须要考虑。

Oracle

如果使用 LoadDataSet 方法加载数据,它将不会转换 Guid 和 Boolean 数据类型。这是因为架构无法决定数据的值是 Guid 还是简单的 bype">">。数据将返回为 byte[ 列。

当你为返回多个游标的存储过程创建 DbCommand 对象时,必须传递一个对象数组到 GetStoredProcCommand 方法。数组的大小必须与由存储过程返回的游标数量相同。例如,下列代码示范了如何为返回二个游标的存储过程传递对象数组到 GetStoredProcCommand 。

Database db = DatabaseFactory.CreateDatabase();

object results = new object2;
DbCommand dbCommand = db.GetStoredProcCommand("GetCustomersAndSuppliers", results);

如果存储过程仅返回一个游标,则不必须传递对象数组。 用于创建可移植数据库应用程序的建议。 在此有一些用于创建可移植数据库应用程序的建议:

  • 避免用存储过程参数名使用数据库专用令牌。用于特定提供程序的 Database 派生类包含了调整需要的参数名的代码。例如,在支持到 SQL Server 数据库的存储过程参数名中不要包含 "@" 字符。下列代码展示了如何调用 AddInParameter 方法通过名称 CategoryID 创建参数。当使用 SqlDatabase 对象执行此代码时,提供程序用 "@" 做为参数名的开头。
Database db = DatabaseFactory.CreateDatabase();

DbCommand 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 的可移植接口,这些做将可能无法正常工作。

处理异常

处理异常的策略在任何企业应用程序中都是必不可少的。下列信息将帮助你添加数据访问应用程序块到管理异常的方法中去:

  • CreateDatabase 方法使用配置信息,其可能的结果在配置相关的异常中。
  • Database 方法使用 ADO.NET 和底层数据库提供程序。由 ADO.NET 抛出的异常由数据访问应用程序块为度量的目的而捕获,然后再次抛出。
  • 充分处理异常通常要求访问特定的异常类型。可以包

使用 CommandBehavior.CloseConnection 调用 ExecuteReader。它在 DataReader 关闭时关闭连接。如果在一个 try 块中使用 ExecuteReader ,可以添加一个 finally 语句并关闭返回的 DataReader 对象,就像展示在下列示例中的一样。

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();
}

另一种方法是,可以包含 using 语句来销毁 DataReader 对象,这将导致它的关闭,就像展示在下列示例中一样。

Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand("GetProductsByCategory"); 

using (IDataReader dataReader = db.ExecuteReader(dbCommand))
{
    // Process results
}

对于在 .NET 中的异常管理的设计和实现原则,请参见异常管理架构指南

处理参数

绝大多数存储过程接受用于输入存储过程或在输出时设置的值的参数。就像使用 ADO.NET 一样,数据访问应用程序块允许开发人员指定参数所有的属性。这些属性可以包括方向、数据类型和长度。此方法叫做显式参数处理。然而,为了方便,可以仅指定用于输入参数的值。在这种情况下,应用程序块将查找并提供参数的属性。此方法叫参数发现。

显式参数处理

Database 类包含了不同的用于传递参数到存储过程的方法。此类还包含了用于设置和测试这些参数的值的方法。这些方法如下:

  • AddParameter。此方法传递一个参数(输入或输出)到存储过程。
  • AddInParameter。此方法传递输入参数到一个存储过程。
  • AddOutParameter。此方法添加了一个输出参数到存储过程。
  • GetParameterValue。此方法查找指定的参数的值。
  • SetParameterValue。此方法在使用同样的连接和命令,但有不同的参数值时进行多个插入时设置指定参数的值。

下列代码示范了如何使用 AddInParameterAddOutParameter 指定参数。

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);

注意:前面的代码不包括专用于数据库类型的参数名称令牌。因此,代码保留了跨多个不同数据库提供程序的通用性。当此代码运行于 SqlClient 数据提供程序时(并因此使用 SqlDatabase 类),下列代码将与前面的代码有着同样的行为。然而,此代码不能移植到其他的数据类型。

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);

使用列值做为参数输入 UpdateDataSet 方法要求三个不同的命令:一个用于插入值,一个用于修改值,另一个用于删除值。通常,这些命令用于存储过程而不是 SQL 字符串。它们在调用后保持由存储过程使用的参数。代替指定用于存储过程参数的值,来自 DataSet 的值被用作输入。在这种情况下,AddInParameter 的适当重载是接受源列做为参数的方法之一。 下列代码展示了如何使用列值做为参数输入。

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);

参数发现 使用数据访问应用程序块,开发人员可以指定用于参数的值,而不需要关于这些参数的任何其他信息。在使用参数发现时,将要指定所有参数,并设置所有输出参数为 NULL 。 下列代码示范了如何仅通过指定参数值而无其他属性来使用 GetStoredProcCommand

Database db = DatabaseFactory.CreateDatabase();
string sqlCommand = "UpdateProduct";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand, 11, "Queso Cabrales", 4, 25);

关于每个参数的信息(例如,它的数据类型)依赖是底层 ADO.NET 方法调用所需要的。为了提供这些信息,数据访问应用程序块使用 ADO.NET 中的 DeriveParameters 方法来查找参数信息。

因为 DeriveParameters 调用需要到后端数据库的一次往返,应用程序块还提供了参数信息缓存。在第一次调用需要参数发现的特定存储过程后,关于每个参数的信息都保存到了参数缓存中。这意味着对同样的存储过程的后继调用将不需要往返。

在使用参数发现时,最好的方法是指定所有的输出参数为 NULL 。不需要为 Oracle 存储过程提供游标参数,OracleDatabase 提供了它们。此对象假设游标参数是存储过程参数列表中的第一个参数。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏拂晓风起

Unity 添加自定义菜单(插件),添加功能

12640
来自专栏谈补锅

ios线程和GCD和队列同步异步的关系

  进程是指在系统中正在运行的一个应用程序。比如同时打开QQ、Xcode,系统就会分别启动2个进程。截图

64540
来自专栏友弟技术工作室

bash及其特性

13630
来自专栏分布式系统进阶

Kafka源码分析-配置文件

作为Class KafkaConfig的伴生类,定义了创建KafkaConfig对象的工厂方法:

9310
来自专栏MasiMaro 的技术博文

PE文件详解(八)

本文转载自小甲鱼PE文件详解系列教程原文传送门 当应用程序需要调用DLL中的函数时,会由系统将DLL中的函数映射到程序的虚拟内存中,dll中本身没有自己的栈...

9720
来自专栏大内老A

ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面

DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容。我们知道,如果直接请求的就是这个默认文件,那么前面介绍的Stat...

20550
来自专栏java思维导图

【一分钟知识】进程与线程,Thread的sleep与wait

java思维导图 xmind导图配合精美文章,可视化学习,让java不再难懂。 进程与线程 进程 是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操...

37290
来自专栏Golang语言社区

Go语言学习(一):hello world

每学一门语言,程序猿们总是不厌其烦地写一遍hello world,这篇就是Go语言的hello world。 Go语言的环境搭建建议参考《Go We...

32660
来自专栏xingoo, 一个梦想做发明家的程序员

Java程序员的日常——经验贴(纯干货)

工作当中遇到的事情比较杂,因此涉及的知识点也很多。这里暂且记录一下,今天遇到的知识点,纯干货~ 关于文件的解压和压缩 如果你的系统不支持tar -z命令 ...

219100
来自专栏Java帮帮-微信公众号-技术文章全总结

request;response 对象

response ? 1. response简介 response的类型为HttpServletResponse,它是Servlet的service()方法的参...

37270

扫码关注云+社区

领取腾讯云代金券