IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Information Management  >

从 BDE 和 dbExpress 转移到 ADO.NET 和 Borland Data Provider

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Jeremy McGee

XML error: Please enter a value for the author element's jobtitle attribute, or the company-name element, or both.



2003 年 10 月 01 日

Borland Data Provider 经过改进的特性为那些需要访问 IBM DB2 的 .NET 开发人员提供了一种高效的、可伸缩的创建 Windows 客户机和 Web 应用程序的方法。在本文中,Jeremy McGee 通过使用 C#Builder Enterprise、DB2 UDB 和用于 Microsoft .NET Framework 的 IBM Managed Data Provider 查看 ADO.NET 类和接口。

简介

如果您使用过 Borland® Delphi、C++Builder 或 Kylix,您就会知道为了连接到像 IBM® DB2® Universal Database TM (UDB) 这样的数据库,最方便的是使用来自 Borland Visual Component Library (VCL) 的一些组件。有了 Borland C#Builder TM,一切都变了:现在不需要使用 Borland VCL,而是使用 Microsoft® .NET Framework,特别是 ADO.NET,而且也不需要使用 Borland Database Engine 或 dbExpress。

Bob Swart 关于连接到 DB2 UDB 数据的两篇文章说明了通过 C#Builder 可以使得创建简单的使用 Borland Data Provider (BDP) 和 ADO.NET 的数据库应用程序变得很容易。( 第一篇文章 展示了如何可视化地连接 BDP 组件以便创建简单的用户界面。 第二篇文章 则更详细地介绍了 DataSet 类。)

在本文中,我们将看看所有那些 ADO.NET 类和接口可以为我们做些什么。我们将使用 C#Builder Enterprise、DB2 UDB 和用于 Microsoft .NET Framework 的 IBM Managed Data Provider。

注意,我们将使用 DB2 管理的代码提供者作为一些示例。要安装该代码管理者,您需要 DB2 UDB 8.1 FixPak 3 或更佳版本的 Windows 客户机,该客户机要安装 Microsoft .NET Framework DB2 UDB 驱动程序。如果您现在使用的是 C#Builder,您将需要添加 IBM.Data.DB2 程序集(assembly)——使用 Project | Add Reference,然后选择 Browse 并导航到 IBM DB2 .NET Framework 1.1 程序集。默认情况下这个程序集安装在 C:\\Program Files\\IBM\\SQLLIB\\BIN\\netf11 中。必要时,我将使用常规的 DB2 UDB 样本数据库。





回页首


使用 ADO.NET:可视化还是非可视化?

VCL 用户马上会碰到的一个主要挑战就是在 .NET Framework 中没有相应的数据模型。这意味着在使用用户界面的可视情况下,要从其他格式的对象引用某个对象时没有容易的方法。虽然也有许多可以替代的选择,但是它们都要依赖于代码。在本文中,我包含了一些代码示例,以启示您该做些什么。幸好,与 VCL 一样,ADO.NET 给人印象深刻的一个方面是非常容易通过程序代码创建数据库访问逻辑。这些代码示例都将使用 C#。一旦您理解了程序代码(尤其是接口和类的精妙之处),则编写用于数据库访问逻辑的“包装器”类就非常简单了。如果以适当的方式编写了包装器类,则最终得到的将是“业务逻辑”类,这些业务逻辑类既可以处理所有的数据库访问,也可以在整个应用程序中使用。关于这一点的一个好例子就是我们在 前一篇文章 中讨论过的 IBuySpy 应用程序。





回页首


简短的概述

下面是经过简化的 ADO.NET 的架构视图,图中将 ADO.NET 与 BDE 作了比较。


图 1. ADO.NET 与 BDE 比较的简单视图
Figure 1. A simple view of how ADO.NET compares with the BDE

如果您熟悉 VCL 组件,那么您将发现 ADO.NET 背后的原理相当简单。ADO.NET 看上去虽然很不同,但是大部分的特性都还在——只不过是放在不同的地方而已。





回页首


与 DBConnection 连接

第一个类返回一个数据库连接对象。所有其他组件都要用这个对象作为句柄,以便引用数据库连接。

例如,在 C# 语言中,对于使用 DB2 管理的提供者的 IBM DB2:

DB2Connection myConn = new 
IBM.Data.DB2.DB2Connection("DATABASE = SAMPLE; 
UID=<username>; PWD=<password>;");

一旦该句柄被赋值,便可以打开该句柄,使用它作为查询或存储过程的 Connection 属性,然后再关闭它。例如:

DB2Connection myConn = new
IBM.Data.DB2.DB2Connection("DATABASE = SAMPLE;
UID=<username>; PWD=<password>;");
string myInsertQuery = "INSERT INTO STAFF (ID, 
NAME) Values(1002,"Jane Doe")";
DB2Command myComm = new DB2Command(myInsertQuery);
myComm.Connection = myConn;
myConn.Open();
myComm.ExecuteNonQuery();
myConn.Close();

我们将在后面讲述 DB2Command 对象。

很直观地,我们可以看到 ADO.NET 和 VCL 之间的一个重要区别。假如要使用 BDE 连接到一个数据库,我们最好使用一个 TDatabase 组件,而不管底层数据库发生了什么。至于 ADO.NET,我们为每种数据库服务器使用数据访问组件的一个专门化的变种。





回页首


不同的数据提供者是如何实现的

为每种类型的数据库服务器专门定制数据库访问类这种方法是 ADO.NET 的设计的主要特性之一。尽管驱动程序类各不相同,但是所有这些类都有相同的基本功能,通常可以交换使用。诀窍是通过定义了特定于数据库的类的 接口来实现。

使用 IBM.Data.DB2 管理的提供者访问 DB2 服务器上数据的应用程序将使用一个 DB2Connection 组件,上一个例子中便是如此。对于 SQL Server,有一个类似的组件,即 SqlConnection:

SqlConnection myConn 
 = new 
System.Data.SqlClient.SqlConnection("Persist 
Security Info=False;Integrated 
Security=SSPI;database=northwind;server=mySQLServer");

对于 Oracle,有一个 OracleConnection 组件:
OracleConnection myConn
 = new 
System.Data.OracleClient.OracleConnection("Data 
Source=Oracle8i;Integrated Security=yes");

所有这些组件都有相同的一套基本属性、方法和事件:换句话说,这些组件共享相同的接口。用于数据库连接的接口是 IDbConnection,在 Microsoft .NET Framework 帮助中对此有文档说明。这个接口定义了最小级别的功能;实际上,每个组件都很可能有附加的属性、方法和事件,用以支持特定数据库或驱动程序的一些特殊的特性。

例如,DB2Connection 组件包括一个附加的方法,该方法可用于强制释放连接池资源。默认情况下,ADO.NET 提供者可以共享和重用连接,这样可以节省资源,但同时也意味着连接要一直开放,以备可能的重用。这个方法为您提供了更多的控制,这是通过像 SQL Server 管理的提供者这样的驱动程序所不能提供的。

虽然特定于数据库的组件提供了很大的灵活性,但是它们并不能带来可移植的代码。或许正是这个讨厌的特点使得您想从一种数据库换到另一种数据库。考虑到这一点,Borland 创建了 Borland Data Provider (BDP)。

BDP 是一个常规的受管 .NET 数据提供者,但是又有一点不同。它不是只使用一种数据库,而是可以使用多种不同的服务器。实际的驱动程序由创建 BdpConnection 对象时传递给这个对象的连接字符串(或者 ConnectionString 属性)决定。

所以为了使用 BDP 连接到 SQL Server,您可以使用:

BdpConnection myConn = new 
Borland.Data.Provider.BdpConnection("assembly=Borl
and.Data.Mssql,Version=1.1.0.0,
Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;ve
ndorclient=sqloledb.dll;
osauthentication=False;database=<database>;usernam
e=<user>;hostname=<host>;
password=<password>;provider=MSSQL");

注意,对于这种特定的驱动程序,主机名嵌入在连接字符串中。对于 DB2 驱动程序来说这不是必需的,您可以使用:

BdpConnection myConn = new 
Borland.Data.Provider.BdpConnection("assembly=Borl
and.Data.Db2,Version=1.1.0.0,
Culture=neutral,PublicKeyToken=91d62ebb5b0d1b1b;ve
ndorclient=db2cli.dll;
database=<database>;username=<user>;
password=<password>;provider=DB2");

虽然连接语法稍微有点不同,但是返回的对象是一样的。也就是说, 所有其他的代码并没有变。您甚至可以将这个字符串存放在一个资源文件中,并在运行时引用它。

Borland 提供了一个 Connections Editor,窗体设计者可以用它来创建连接字符串。我可以在 C#Builder 的另一个实例中另外创建一个项目,将一个 bdpConnection 对象放到 Windows Form 上,然后使用剪切板将连接字符串复制到我正在编写的应用程序的一个资源文件中。

在大多数情况下,BDP 在内部调用与特定于服务器的组件一样的本地客户机库,因此速度是一样快的。

DbConnection 类与 BDE TDatabase 或 dbExpress TSQLConnection 组件大致相当。与这些组件一样,DbConnection 是管理事务的地方。不过事务管理的方法却颇有不同:DbConnection 类的 BeginTransaction 方法返回一个事务对象,这个对象可用作后面的 SQL 命令的一个属性。





回页首


使用 DbCommand 执行查询

一旦打开了连接,ADO.NET 将通过组件直接运行对数据库的查询。与 DbConnection 类一样,DbCommand 是一个抽象接口,有其特定于服务器的实现。我们已经见过这个例子了:

DB2Connection myConn = new 
IBM.Data.DB2.DB2Connection("DATABASE = SAMPLE");
string myInsertQuery = "INSERT INTO STAFF (ID, 
NAME) Values(1002,"Jane Doe")";
DB2Command myComm = new DB2Command(myInsertQuery);
myComm.Connection = myConn;
myConn.Open();
myComm.ExecuteNonQuery();
myConn.Close();

我们可以看到,DB2Command——这个特定于 DB2 风格的 DbCommand——可用于直接运行对 DB2 服务器的查询。对于 BDP 也有相应的接口,因此

BdpConnection myConn = new 
Borland.Data.Provider.BdpConnection(connectionstring);
string myInsertQuery = "INSERT INTO STAFF (ID, 
NAME) Values(1002,"Jane Doe")";
BdpCommand myComm = new BdpCommand(myInsertQuery);
myComm.Connection = myConn;
myConn.Open();
myComm.ExecuteNonQuery();
myConn.Close();

可以运行在任何有 BDP 驱动程序的服务器上。

SQL INSERT 命令不能返回结果,但是可以带参数。ADO.NET 可以使用 DbDataParameter 类通过 Parameter 属性来包含参数。同样,参数类也是在特定于服务器的基础上实现的。

下面是一个关于参数的例子,它调用一个 DB2 存储过程以便从数据库返回完整的名称,假设是一个电子邮件地址。首先是存储过程:

CREATE PROCEDURE Job(
  IN v_Name VARCHAR(50),
  OUT v_Job VARCHAR(50))
DYNAMIC RESULT SETS 1 
LANGUAGE SQL 
BEGIN 
  SELECT Job INTO v_Job FROM Staff WHERE Name = v_Name
  FETCH FIRST 1 ROWS ONLY;
END

然后是 C# 代码片断。我们创建连接,创建一个命令,然后创建两个 BdpParameter 对象并将它们添加到命令对象。

public String GetJob(String Name) 
{
	BdpConnection myConn = new BdpConnection(<connection string>);
	BdpCommand myCommand = new BdpCommand("Job", myConn);
	// This is a stored procedure - mark the command accordingly
	myCommand.CommandType = CommandType.StoredProcedure;
	// Add the input parameter
	BdpParameter paramName = new BdpParameter("V_NAME", BdpType.String, 50);
	paramName.Direction = ParameterDirection.Input;
	paramName.Precision = 50;
	myCommand.Parameters.Add(paramName);
	// The output parameter
	BdpParameter paramJob = new BdpParameter("V_JOB", BdpType.String, 50);
	paramJob.Direction = ParameterDirection.Output;
	paramJob.Precision = 50;
	myCommand.Parameters.Add(paramJob);
	paramName.Value = Name;
	myConn.Open();
	myCommand.ExecuteNonQuery();
	myConn.Close();
	// The return value is passed as an Object. We need to cast as a string
	return (string)paramJob.Value;
}

这里有两个重要的地方需要注意。首先,参数声明的顺序应该与存储过程中声明的顺序一致。其次,您需要显式地将参数类型覆盖为服务器所期望的特定的底层数据类型。下面是 DB2 管理的提供者和 BDP 所支持的一些不同的数据类型的例子:

DB2 数据类型 DB2 管理的提供者 Borland Data Provider
VARCHARDB2Type.VarCharBdpType.String
INTDB2Type.IntegerBdpType.Int16, BdpType.Int32, 或 BdpType.Int64, 根据需要而定
IMAL (x, y)DB2Type.DecimalBdpType.Decimal
TIMESTAMPDB2Type.TimeStampBdpType.DateTime

您还将需要为这些数据类型设置 Size 和 Precision 属性。

这种方法与 BDE and dbExpress 有很大的不同,后者使用 TQuery 和 TSQLQuery 组件来调用存储过程或者其他数据定义查询,这种调用不会返回列表结果集。这些应该用 DbCommand 对象代替。然而,我们可以看到,如果数据需要更新的话,通常 SELECT 查询要么需要使用一个 DataReader,要么需要使用一个 DataSet。





回页首


使用 DataReader 直接访问 DbCommand 数据 

我刚才为 DbCommand 类列出的这些例子没有返回一个数据集。DbCommand 可以处理这一点,但是您将需要使用 DataReader 组件来获取数据。

乍一看来,DataReader 好像受到一些限制。您只能向前移动,每次读取一条记录,没有数据缓存。然而,这些限制比起它的主要特性,即速度来就不足道了——DataReader 速度极快。

奇怪的是一个程序频繁地使用适合于 DataReader 的一条 SELECT 查询。其中一个例子就是从一个准备显示在 Web 页面上的表返回一个记录块:

public String GetStaff(string Job)
{
BdpConnection myConn = new 
BdpConnection(<connection string>);
BdpCommand myCommand = new BdpCommand("SELECT 
NAME, YEARS FROM STAFF WHERE JOB=? FETCH FIRST 10 
ROWS ONLY", myConn);
BdpParameter paramJob = new BdpParameter("JOB", 
BdpType.String, 50);
paramJob.Direction = ParameterDirection.Input;
paramJob.Precision = 50;
myCommand.Parameters.Add(paramJob);
paramJob.Value = Job;
myConn.Open();
BdpDataReader readerStaff = 
myCommand.ExecuteReader();
string staffList="";
while (readerStaff.Read()) {
	staffList += readerStaff.GetString(0)+" 
("+readerStaff.GetInt16(1)+") <br />";
}
readerStaff.Close();
myConn.Close();
return staffList;
}

这个例子使用了 BDP。DB2 管理的提供者的语法非常的相似:只是把 "Bdp? components to be "Db2? 组件和 BdpType.String 改为 DB2Type.VarChar。

这个例子显示了如何使用 SELECT 查询中的参数——这个过程与存储过程中使用的过程很相似。您需要添加 Parameter 对象到 Command.Parameters 属性,添加的顺序与查询中定义 "?" 占位符的顺序一致。注意,我们将显式地告诉系统需要什么样的数据,这样可以使代码运行得更快。

对于 BDE 没有 IDataReader 的同等物:尽管 TQuery 有一个 UniDirectional 属性,但这只能影响 BDE,而对于对数据库的底层查询没有作用,因此这里总是要准备一定程度的缓冲。

在 dbExpress 中最接近的同等物是 TSQLQuery 组件,该组件同样返回一个前向结果集。然而,我们已经看到,使用 ADO.NET 检索结果集更加容易。





回页首


DbDataAdapter 和 DataSet

如果您想缓存数据并交互作用地浏览数据时该怎么办呢?这就是 DataSet 对象可以做的。这种强大的对象可以管理一个本地数据缓存,缓存的数据是通过使用一个 DbDataAdapter 对象从一个数据库中检索到的。然后可以使用这个缓存作为其他可视化 .NET Framework 组件(例如 DataGrid 或 TextBox)的数据源。

在缓存数据的时候,DataSet 对象与 VCL TClientDataSet 相似。然而,它要强大得多:例如,它可以存储来自多个表中的数据。也就是说,当我们建立 DataSet 对象时,我们需要告诉这个对象要针对什么表以及要将哪些列存入该对象中。例如:

// Create a new dataset
DataSet StaffDataSet = new DataSet("DSStaff");
// Set up the columns for the table for the dataset
DataColumn IDCol = new DataColumn("ID", typeof(short));
DataColumn NameCol = new DataColumn("NAME");
DataColumn YearsCol = new DataColumn("YEARS", typeof(short));
// Set up the table for the dataset and add the columns
DataTable StaffTable = new DataTable("TabStaff");
StaffTable.Columns.AddRange(new DataColumn[] {IDCol, NameCol, YearsCol});
// Add the table to the dataset
StaffDataSet.Tables.AddRange(new DataTable[] {StaffTable});

您可以使用很多参数来微调这些对象中每一个对象的行为——参见联机帮助以获得一个概述。我将使用可以将所需代码减至最少的构造函数。注意,DataSet 组件只有一种风格。不管您选择哪个受管的提供者,DataSet 组件都是一样的。

我们的代码只配置 DataSet,但是不检索数据。要检索数据,我们需要一个 DbDataAdapter。这个组件不仅可以从数据库检索数据,还可以从 DataSet 将改过的数据放回数据库。

因为还必须将不同的数据类型映射到 .NET Framework 数据类型,DbDataAdapter 对象因所使用的数据库服务器的不同而采用不同的风格。为了使用 DbDataAdapter 组件,通常需要设置一些属性,以便定义用于检索、更新、插入和删除记录的查询。如果您想更新数据,则可以调用这些查询。

所有这些查询都存储在一个 DbCommand 对象中,因此,如果您想处理更新,需要创建 4 个查询,每一个查询都要参数化——这很难掌握。

不过,如果您只是想读取数据,那么事情就比较简单了。下面的代码片断展示了如何使用 BDP 来做这种工作:

BdpConnection myConn = new 
BdpConnection(ConnString);
// Create a command object to retrieve data
BdpCommand SelComm = new BdpCommand("SELECT ID, 
NAME, YEARS FROM STAFF", myConn);
// Create the data adapter
BdpDataAdapter StaffDA = new BdpDataAdapter();
// Point the data adapter to the command object to 
get data
StaffDA.SelectCommand = SelComm;

首先,我建立连接,然后建立一个 BdpCommand 对象,用以检索数据。DataAdapter 用于将 BdpCommand 链接到 DataSet 中的 DataTable。至此您可能会预想建立 DB2DataAdapter 的过程是类似的:用 DB2 中的同等物替代 BDP 组件。

创建好 DbDataAdapter 之后,您需要填充 DataSet。这里,在 BdpDataAdapter 与其他风格之间会有所不同。对于 DB2DataAdapter 和其他特定于服务器的驱动程序,您需要自己在代码中告诉 DbDataAdapter 填充 DataSet。

例如,下面的代码可以添加到使用 DB2 管理的提供者的一个窗体的 Open 方法中:

StaffDA.Fill(StaffDataSet);
this.dataGrid1.DataMember = "TabStaff";
this.dataGrid1.DataSource = StaffDataSet;

BDP 使得事情更容易一点。一个额外的 active 属性可用于告诉 DataAdapter 自动地根据请求填充一个 DataSet 中的一个 DataTable。因此这里不是在 Open 方法中设置属性,而是将以下几行添加到初次建立连接时运行的代码当中:

// Point the data adapter to the DataSet and the table within it
StaffDA.DataSet = StaffDataSet;
StaffDA.DataTable = StaffTable;
// Tell the DataAdapter to automatically populate the table
StaffDA.Active = true;

这样做的一个很大的副作用是 DataSet 可以在设计的时候填充,从而暴露了活动数据。尽管这种情况只有当 BdpDataAdapter 被与 DataSet 和相关的控件放在同一个窗体上的时候才会发生,但这的确是个陷阱。实际上您可能会发现,对于更大的系统,您将使用一个中央对象来封装 DataSet,以便您可以在多个窗体之间共享它。

如果您以前使用的是 BDE,您可以认为 DataSet 组件与 TClientDataSet 有点相像。它们之间在表面上有些类似之处:与 TClientDataSet 一样,DataSet 将数据缓存在工作站本地,这是一种使用数据的有效方法,因为数据是在一个大块中读取和更新的。与 BDE 一样,当它返回一个只读结果集时,DataAdapter 为开发者提供了 SQL 查询以更新源数据库。

然而,在 TClientDataSet 的背后却颇有不同。与 BDE TClientDataSet 不一样,DataSet 可以缓存多个表。DataSet 在内部管理数据,把数据当作 XML 模式的关系视图,因此它没有“游标”或当前记录的概念。相反,当前记录指针是由数据绑定层管理的。

另一个不同之处就是处理更新时所用的技术。TClientDataSet 保留了对表的更新日志,这种方法使得处理多层的“undo”非常方便。相反,当使用数据绑定控件进行更新时,DataSet 将更新一行接一行地保存,只保留被更新和删除的行的最初版本。





回页首


从 BDE 转移到 ADO.NET 和 BDP

如果您习惯使用 BDE,您会希望看看一些组件,下面就是对这些组件的一个简短的总结。这里并没有列出所有的组件,而是很自然地经过了简化,但应该可以帮助您理解从哪开始。

BDE 组件功能ADO.NET 组件功能
TSession 和TDataBase建立到数据库的连接和管理事务DB2Connection, BdpConnection建立到数据库的连接和管理事务
Ttable管理一个表的结果集的本地缓存没有直接的同等物。要么同时使用 DataSet 和 DbDataAdapter,要么同时使用 DbCommand 和 DataReaderDataSet 根据查询创建一个或多个表的本地缓存
Tquery管理一个查询的本地缓存没有直接的同等物。要么使用 DataSet / DbDataAdapter,要么使用 DbCommand / DataReaderDbCommand / DataReader 管理查询结果的前向游标
TstoredProc执行一个存储过程DbCommand不能执行存储过程,可以可选地返回一个结果集
TdataSource充当 dataset 与数据感知控件(data aware control)之间的通道没有直接的同等物数据感知控件链接到 DataSet 对象中的列
TDBText, TDBEdit, etc.Windows 控件的数据感知版本使用常规的 Windows Forms所有适当的 Windows Forms 都是数据感知的




回页首


结束语

ADO.NET 是一个丰富的、先进的数据库访问库。虽然 ADO.NET 与 Borland Database Engine 有很大的不同,但是 Borland Data Provider 经过改进的特性使得它更易于使用。BDP 为需要访问 DB2 的 .NET 开发人员提供了一种高效的、可伸缩的创建 Windows 客户机和 Web 应用程序的方法。





回页首


免责声明

本文包含样本代码。IBM 授予您(“被许可方”)使用这个样本代码的非专有的、版权免费的许可证。然而,该样本代码是以“按现状”的基础提供的,没有任何形式的(不论是明示的,还是默示的)保证,包括对适销性、适用于特定用途或非侵权性的默示保证。IBM 及其许可方不对被许可方由于使用该软件所导致的任何损失负责。任何情况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许可方都不对由使用该软件或不能使用该软件所引起的收入的减少、利润的损失或数据的丢失,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使 IBM 已经被明确告知此类损害的可能性,也是如此。



关于作者

Jeremy McGee 一开始是在 Commodore PET 上使用 BASIC 编写应用程序的。他还能亲切地想起在一台带有冷藏柜那么大的激光打印机的早期苹果机上为一本书那么长的出版物排版的情形。从那以后,他有了各种不同的工作经验:DEC VAX 系统管理员,Borland Paradox 的技术支持工程师,以及在欧洲的 Borland Delphi 发布小组的成员。Jeremy 现在在美国南安普敦运营着一家咨询公司。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款