Victor Vatamanescu
独立顾问, Romania
2003 年 12 月 如果您对使用 .NET framework 的开发源代码的实现感兴趣,并且喜欢接受创建自己的数据提供程序的挑战,那么本文可以为您提供使用 DB2 Call Level Interface 来实现那样一个数据提供程序的基础知识
简介
.NET 是一种面向对象的、基于组件的现代范例,用于编写 Microsoft® 派别的的软件。.NET 以 .NET Framework 为支撑,后者具有两大功能:
- 管理 .NET 代码的执行。
- 提供一套库可以简化您在项目中碰到的很多编程任务。
事实证明,虽然 .NET 技术存在一些缺陷(glitch),但它仍然有不少吸引开发人员的优点。.Net Framework 是随 Windows® 免费提供的。并且 .NET Framework 还有很多开放源代码的实现。其中最重要的一个实现是 MONO( http://www.go-mono.com),该实现包括一个跨平台的 C# 编译器和库的一个近乎完整的实现。
这套组成 framework 的库是按照功能性而逻辑地组织的。System.Data 库处理与数据相关的编程任务。它提出了一种无连接的(disconnected)、基于 XML 的数据处理架构。
目前,Microsoft 只为它的 SQL Server 数据库系统发布了直接数据提供程序(data provider)。Microsoft 还发布了 OLE DB 和 ODBC 数据提供程序,这两种数据提供程序可以访问任何具有 OLE DB 或 ODBC 驱动程序的数据源中的数据。不过,只有使用直接数据提供程序时才能取得最佳的性能。
在撰写本文之际,IBM® 为其 DB2® Universal Database™ (UDB) 提供的官方 .NET 数据提供程序正处于 beta 阶段,可以从 http://www7b.software.ibm.com/dmdd/downloads/dotnetbeta/下载这个版本。在 DB2 UDB 8.1.2中也提供了这一版本的数据提供程序。
本文的目的是让您熟悉关于数据提供程序的一组概念和库,并向您展示如何编写那样的提供程序。读者如果想获得关于编写自己的数据提供程序的官方说明,请参阅 .NET Framework Developer's Guide。
对用于 DB2 UDB 的 .NET 托管数据提供程序的需求
当编写一个用于 DB2 UDB 的 .NET 托管数据提供程序时,我们通过一套遵从 ADO.NET 数据提供程序架构的类和接口来暴露 DB2 UDB Call Level Interface API (CLI API),ADO.NET 数据提供程序架构是一套特定于 .NET framework 的编码惯例,每一个托管提供程序实现都必须服从这一惯例。
托管数据提供程序必须实现以下接口:
表 1. 托管数据提供程序接口
| 接口 |
描述 |
| IDbConnection |
代表与一个数据源的惟一会话。在客户机/服务器数据库系统的情况下,这种会话相当于到服务器的一个网络连接 |
| IDbTransaction |
代表一个本地事务 |
| IDbCommand |
代表连接到一个数据源时要使用的一个查询或命令 |
| IDataParameter |
允许用户实现某个命令的一个参数以及该参数到 DataSet 列上的映射 |
| IDataParameterCollection |
允许用户实现某个命令的一个参数以及该参数到 DataSet 列上的映射 |
| IDataReader |
提供了一个方法,该方法从数据源读取 forward-only、read-only 型的数据流 |
| IDataAdapter |
允许用户实现一个 DataAdapter,用于填充 DataSet 和将 DataSet 中的更改解析(resolve)回数据源 |
| IDbDataAdapter |
允许用户实现一个适合关系数据库的 DataAdapter。该接口代表一套命令和数据库连接,这些命令和数据库连接用于填充 DataSet 和使对 DataSet 的更改与源数据库一致(即将更改解析回源数据库) |
.NET Framework 还包括 DbDataAdapter 类,这个类为 IDataAdapter 和 IDbDataAdapter 类提供了一个近乎完整的实现。任何实现了 .NET 数据提供程序全套接口的提供程序都可以使用 DbDataAdapter 类。在 .NET Developer's Guide中给出了关于必须实现的公共构造函数的建议。
我们使用下面的这些类来实现我们的提供程序。这些类的命名惯例与本地托管数据提供程序必须遵从的命名规则一致:
- Db2Connection
- Db2Transaction
- Db2Command
- Db2DataReader
- Db2Parameter
- Db2ParameterCollection
- Db2DataAdapter
- Db2Exception
我们将集中介绍 Db2Connection 和 Db2Command 这两个类,因为这两个类在与 DB2 UDB 服务器的交互中是最为重要的。
与 DB2 Call Level Interface (CLI) 交互
我们将使用 C# 语言来实现我们的托管数据提供程序。这一节描述如何与 CLI 交互:
- 访问 DB2 CLI
- 使用 CLI 句柄
- CLI 诊断需求
访问 DB2 CLI
第一步是提供对数据库 API(即 DB2UDB CLI API )的访问。这个 API 是以一个名为 “db2cli” 的动态链接库的形式暴露的。 DB2 Call Level Interface (DB2 CLI) 是 IBM 为 DB2 数据库服务器家族提供的可调用的 SQL 接口。该 API 是用于关系数据库访问的 C 和 C++ 应用程序编程接口,它使用函数调用来传递动态的 SQL 语句作为函数参数 。
CLI 提供的功能是作为包括在 CLIInvoke.cs 中的一套内部枚举、结构和类而访问的。 CLIInvoke.cs 是一个内部类,它充当高级类与低级 API 之间的包装器。下面是一个代码片断:
using System.Data;
using System.Data.Common;
using System.Runtime.InteropServices;
internal enum CliHandleType : short
{
SQL_HANDLE_ENV = 1,
SQL_HANDLE_DBC = 2,
SQL_HANDLE_STMT = 3,
SQL_HANDLE_DESC = 4
};
...
internal class CLIInvoke
{
internal static Db2ParameterDirection
ConvertParameterDirection (ParameterDirection dir)
{
switch (dir)
{
case ParameterDirection.Input:
return Db2ParameterDirection.Input;
case ParameterDirection.InputOutput:
return Db2ParameterDirection.InputOutput;
case ParameterDirection.Output:
return Db2ParameterDirection.Output;
case ParameterDirection.ReturnValue:
return Db2ParameterDirection.ReturnValue;
default:
return Db2ParameterDirection.Input;
}
}
[DllImport("db2cli")]
internal static extern CliReturn SQLAllocHandle
(CliHandleType HandleType, IntPtr InputHandle, ref IntPtr
OutputHandlePtr);
[DllImport("db2cli")]
internal static extern CliReturn SQLSetEnvAttr (IntPtr
EnvHandle, CliEnv Attribute, IntPtr Value, int StringLength);
|
注意 [DllImport("db2cli")] 属性(用了粗体),该属性表明与之关联的特定函数在 “db2cli”动态链接库中。这个属性带有 extern关键字,表明该方法是在外部实现的。
托管提供程序通过互操作性服务(.NET Framework 提供的一套功能)的方式访问 CLI 库中未托管代码。注意 using System.Runtime.InteropServices; 这一行代码,这行代码导入名称空间,这样我们就不必提供全限定的类名。
从概念上讲,我们可以将一个使用 DB2 CLI 的应用程序的操作分为两类:
- 初始化(initialization)和终止(termination)—— 建立和终止与数据库服务器的连接。
- 事务处理 —— 特定于数据操纵和定义语言(data manipulation and definition language)的对数据库服务器的公共操作 。
这些功能将被粗略地映射到 Db2Connection类(初始化和终止)和 Db2Command类(事务处理)。
使用 CLI 句柄
当使用 DB2 CLI 来访问数据库服务器功能时,这种编程范例要求使用句柄。您必须获得以参数形式传递给 CLI API 中函数的句柄。 句柄(handle)是指向由 DB2 CLI 控制的某个数据对象的一个变量。通过使用句柄方式,应用程序就不必对全局变量或数据结构进行太多的管理。
总共有 4 种类型的句柄, IBM DB2 CLI documentation中对此作了描述:
- 环境句柄—— 环境句柄指向一个数据对象,该数据对象包含与应用程序的全局状态有关的信息,例如属性和连接。在为连接句柄分配内存之前,必须先为环境变量分配内存。
- 连接句柄—— 连接句柄指向一个数据对象,该数据对象包含与到某个特定数据源(数据库)的连接相关的信息。这包括连接属性、一般状态信息、事务状态和诊断信息。
- 语句句柄—— 语句句柄指向一个数据对象,该数据对象用于跟踪某一条 SQL 语句的执行。这包括诸如语句属性、SQL 语句文本、动态参数、游标信息、动态参数和列的绑定、结果值以及状态信息(这些将在后面讨论)之类的信息。每一个语句句柄都与一个连接句柄相关联。在执行一条语句之前,必须为语句句柄分配内存。
- 描述符句柄—— 描述符句柄指向一个数据对象,该数据对象包含关于如下的信息:一个结果集中的列和一条 SQL 语句中的一些动态参数。
前两种类型的句柄是专门用于初始化和终止操作的,它们将在 Db2Connection 类中被处理。后两种类型的句柄专门用于事务处理操作,它们大部分将在 Db2Command 类中被处理。
CLI 诊断需求
DB2 CLI 编程范例强加的另一条规则是,每一个 DB2 CLI 函数都要返回函数返回代码,作为基本诊断。返回代码的值包含在 CliReturn 枚举中:
internal enum CliReturn : short
{
SQL_ERROR = -1,
SQL_INVALID_HANDLE = -2,
SQL_SUCCESS = 0,
SQL_SUCCESS_WITH_INFO = 1,
SQL_STILL_EXECUTING = 2,
SQL_NEED_DATA = 99,
SQL_NO_DATA = 100
}
|
每当我们通过 CLIInvoke 的类方法调用一个 DB2 CLI 函数时,我们将检查这个返回值并采取适当的行动。
使用连接:Db2Connection 类
托管数据提供程序要实现的第一个类是连接类 —— 在我们这个例子中是 Db2Connection类。
概述
连接(Connection)对象代表一个数据库连接。
根据 .NET Developer's Guide,我们必须确保在执行命令之前创建并打开了一个连接。此外还有其他一些需要考虑的方面。请参阅这个文档以了解更多信息。
IDbConnection 接口
对于 Db2Connection类的实现最重要的一点需求是它必须实现 IDbConnection接口。以下是 IDbConnection接口中定义的属性和方法:
表 2. IDbConnection 属性
| 属性 |
描述 |
| ConnectionString |
获取(get)或设置(set)用于打开一个数据库的字符串 |
| ConnectionTimeout |
获取尝试建立一个连接时的等待时间,如果超过了这个时间,应用程序将终止尝试并产生一个错误 |
| Database |
获取当前数据库或一个连接打开时要使用的数据库的名称 |
| State |
获取连接的当前状态 |
表 3. IDbConnection 方法
| 方法 |
描述 |
| BeginTransaction |
需要被重载。开始一个数据库事务 |
| ChangeDatabase |
为一个打开的 连接对象更改当前数据库 |
| Close |
关闭与数据库的连接 |
| CreateCommand |
创建并返回一个与连接相关的命令(Command)对象 |
| Open |
使用由特定于提供程序的 连接对象的 ConnectionString属性指定的设置打开一个数据库连接 |
实现 Db2Connection 类
下面的代码片断是 Db2Connection 类的实现。它包含在 Db2Connection.cs 文件中。
后面几个小节将对以下话题进行更多的描述:
- DB2 CLI 环境和连接句柄
- Db2Connection 构造函数
- IDbConnection 接口的实现
- 获得句柄
- 打开一个连接
- 关闭一个连接
- 释放句柄
Db2Connection 构造函数
Db2Connection 类实现了两个构造函数,这是 .NET Developer's Guide 文档中推荐的。下面这两个函数的签名:
public Db2Connection()
public Db2Connection(string connectionString)
|
缺省的构造函数实现包含为环境句柄分配内存和设置环境属性的代码。
public Db2Connection()
{
_connectionTimeout = 15;
_connectionString = null;
_database = null;
CliReturn ret;
ret = CLIInvoke.SQLAllocHandle(CliHandleType.SQL_HANDLE_ENV,
IntPtr.Zero, ref _hEnvironment);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot allocate environment
handle");
ret = CLIInvoke.SQLSetEnvAttr(_hEnvironment, CliEnv.CliVersion,
(IntPtr) 3 , 0);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot set the environment's
attributes");
}
public Db2Connection(string connectionString): this ()
{
this.ConnectionString = connectionString;
}
|
IDbConnection 接口的实现
IDbConnection接口的每个成员在 Db2Connection类中都有其实现。注意下面这个方法:
这个方法不是 IDbConnection 接口的直接成员。它是由 IDbConnection 接口从 IDisposable 接口继承过来的,因此也必须实现。
这里还应该注意 ConnectionString 属性的 setter 方法。该方法解析连接字符串,并填充适当的私有字段。为简单起见,该方法的实现被限制为最小。
获得句柄
这一任务是 DB2 CLI 初始化工作的一部分。
DB2 CLI 环境和连接句柄
在 Db2Connection 类的实现中第一件要注意的事就是,除了直接映射到这个类的公共属性的私有字段外,我们还有两个 DB2 CLI 句柄:
- _hEnvironment:环境句柄。
- _hConnection:连接句柄。
这两个句柄都是 IntPtr类型,这是一种特定于平台的类型,用于表示一个指针或句柄。 _hEnvironment和 _hConnection变量是在缺省的构造函数中初始化的。我们将使用这两个句柄来与 DB2 CLI 函数交互。注意, _hConnection句柄是作为 hConnection内部属性暴露的。这样一来,只有那些对于托管数据提供程序程序集(assembly)是内部的类型才可以访问它。
下面是为环境句柄分配内存以及设置环境属性的代码:
ret = CLIInvoke.SQLAllocHandle(CliHandleType.SQL_HANDLE_ENV,
IntPtr.Zero, ref _hEnvironment);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot allocate EnvHandle...");
ret = CLIInvoke.SQLSetEnvAttr(_hEnvironment, CliEnv.CliVersion,
(IntPtr) 3 , 0);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot set the environment's
attributes...");
|
这段代码放在 Db2Connection 类的缺省构造函数中。
- 第一步是为环境句柄分配内存。基本上,我们通过
CLIInvoke 类调用 SQLAllocHandle DB2 CLI 函数。然后,测试返回代码,如果调用失败,我们就抛出异常。
SQLAllocHandle 函数用于获得四种句柄中任意一种类型的句柄。在这种情况下,我们使用 CliHandleType 枚举来通知该函数我们想要一个环境函数并且通过引用将 _hEnvironment 私有字段传递给该函数,以获得句柄。
- 第二步是设置环境变量。我们使用
CLIInvoke 包装器类调用 SQLSetEnvAttr DB2 CLI 函数。注意,这个函数的第一个参数是我们前面获得的环境句柄。我们还测试该函数的返回代码。
- 连接句柄是以类似的方法获得的。不同之处是我们将在 Open 方法中获得这个句柄。
ret = CLIInvoke.SQLAllocHandle(CliHandleType.SQL_HANDLE_DBC,
_hEnvironment, ref _hConnection);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot allocate database handle");
|
注意,我们同样使用了 DB2 CLI SQLAllocHandle,只有这一次要请求一个连接句柄,我们提供环境句柄作为输入参数。我们测试返回代码,以了解是否需要抛出错误。
处理返回代码:我们使用 CliReturn 枚举来处理返回代码。每当调用一个 DB2 CLI 函数时,我们读取它的返回代码,如果该函数返回一个错误,那么就抛出一个 Db2Error 错误。
打开一个连接
® 打开一个连接所需的代码包含在 Open 方法中。当我们获得连接句柄之后,我们就可以使用它来打开连接。
public void Open()
{
CliReturn ret;
if (State == ConnectionState.Open)
throw new Db2Exception("Connection already open");
ret = CLIInvoke.SQLAllocHandle(CliHandleType.SQL_HANDLE_DBC,
_hEnvironment, ref _hConnection);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot allocate database handle");
ret = CLIInvoke.SQLConnect(_hConnection, _server, (short)
_server.Length, _user, (short) _user.Length, _password,
(short) _password.Length);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Exception("Cannot connect to the database");
}
|
用于打开连接的 DB2 CLI 函数是 SQLConnect,这个函数是通过相应的 CLIInvoke 包装器类访问的。这个函数以连接句柄作为一个参数,另外的参数还有登录凭证(credential)。调用了这个函数之后,我们就测试返回值。我们也可以使用其他函数来打开一个连接, DB2 CLI 文档中对这些函数作了描述。
另一个与数据库连接相关的问题就是,您可以实现一个连接拉取(connection pulling)机制。当编写提供程序的时候,这一话题可能与多线程的使用有关,但是这些话题超出了本文的范围。
关闭一个连接
至此,我们已经有了环境句柄和连接句柄。从现在开始,使用 DB2 CLI 的初始化工作已经告终。这只是 Db2Connection 类的一半职责所在。这个类的另一半职责是终止我们已经初始化的、正在运行的会话。
要在 ADO.NET 中关闭一个连接,可调用 Db2Connection 类的 Close 方法。这个方法将通过 CLIInvoke 包装器类调用 SQLDisconnect DB2 CLI 函数。
public void Close ()
{
if (this.State == ConnectionState.Open)
{
CliReturn ret;
ret = CLIInvoke.SQLDisconnect(_hConnection);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
{
throw new Db2Exception("Error when trying to dispose
the environment handle");
}
}
else
{
throw new Db2Exception("InvalidOperationException");
}
}
|
像往常一样,我们检查返回代码,如果出现错误,就抛出一个异常。还应注意的是(请看整个方法主体),我们首先检查了该连接的状态。
释放句柄
为了终止正在运行的会话,还有一件必须要做的事就是释放到目前为止我们已经使用过的句柄。这里将使用 DB2 CLI SQLFreeHandle 函数,这个函数是通过 CLIInvoke 包装器类访问的。还要检查返回代码并作出相应的行动。
ret = CLIInvoke.SQLFreeHandle((ushort) CliHandleType.SQL_HANDLE_DBC,
_hConnection);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Error when trying to dispose the
connection handle");
ret = CLIInvoke.SQLFreeHandle((ushort) CliHandleType.SQL_HANDLE_ENV,
_hEnvironment);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Error when trying to dispose the
environment handle");
|
注意,我们是在 Dispose 方法中释放句柄的。这个方法是 .NET framework 中用于做这种清除工作的地方,因为我们没有确定性的(deterministic)析构函数 —— 垃圾收集器将触发析构函数,但我们不知道何时触发。通过将这种代码放在 IDisposable 接口的 Dispose 方法中,我们就可以从其他地方调用这个方法并释放宝贵的资源。
事务处理:Db2Command 类
从 ADO.NET 的角度来看,命令对象负责制定请求并将其传递到数据源上。如果要返回结果,则命令对象负责以一个 DataReader、一个标量值或参数的形式返回结果。为了实现一个命令对象,需创建一个实现了 IDbCommand 的类。实现 ExecuteReader 方法,使它以 DataReader 的形式返回一个结果集(或多个结果集),实现 ExecuteScalar 方法,使它以标量值的形式返回一个结果,还要实现 ExecuteNonQuery 方法,使它处理数据源上的命令,这个方法不返回结果集,但可能返回一些参数。
IDbCommand 接口
IDbCommand 接口具有下列一套必须实现的属性和方法:
表 4. IDbCommand 属性
| 属性 |
描述 |
| CommandText |
获取或设置要对数据源执行的文本命令 |
| CommandTimeout |
获取或设置等待时间,如果超过这个时间,应用程序将停止尝试执行某个命令并产生一个错误 |
| CommandType |
表明或指定如何解释 CommandText 属性 |
| Connection |
获取或设置这个 IDbCommand 实例所使用的 IDbConnection |
| Parameters |
获取 IDataParameterCollection |
| Transaction |
获取或设置 ADO.NET 数据提供程序的命令对象运行时所在的事务 |
| UpdatedRowSource |
获取或设置当一个 DbDataAdapter 的 Update 方法使用命令结果时,将该命令结果应用到 DataRow 所采用的方式 |
表 5. IDbCommand 方法
| 方法 |
描述 |
| Cancel |
试图取消一个 IDbCommand 的执行 |
| CreateParameter |
创建 IDataParameter 对象的一个新实例 |
| ExecuteNonQuery |
对 ADO.NET 数据提供程序的连接对象执行一条 SQL 语句,并返回受影响的行的数目 |
| ExecuteReader |
需要重载。对连接对象执行 CommandText 并建立一个 IDataReader |
| ExecuteScalar |
执行查询,返回该查询返回的结果集中第一行的第一列。其他的列或行将被忽略 |
| Prepare |
创建一个准备好的(或编译好的)针对数据源的命令 |
实现 Db2Command 类
Db2Command 类实现 IDbCommand 接口,对 DB2 CLI 的访问是通过 CLIInvoke包装器类实现的。我们更仔细地看一下 Prepare 方法。
public void Prepare()
{
throw new NotSupportedException();
}
|
尽管一个完全的 DB2 UDB 托管数据提供程序不会提供这样的实现,但是这里的用意是,如果底层的数据源没有提供某个特定的功能,那么托管数据提供程序应该抛出一个 NotSupportedException 异常。
Db2Command 类中的事务处理任务
我们仍然是使用句柄来访问 DB2 CLI 的功能,但是只有在这里我们才使用语句句柄和描述符句柄。从概念上讲,事务处理有以下一些步骤:
- 为语句句柄分配内存。
- SQL 语句的准备和执行。
- 处理结果。
- 提交或回滚。
- 可选地,如果语句不会再次执行的话,就释放语句句柄。
我们将主要关注一些最基本的任务:
- 为语句句柄分配内存。
- 执行一条语句。
- 释放语句句柄。
为语句句柄分配内存 在将语句句柄声明为私有的 IntPtr指针( _hPointer)以后,我们必须为之分配内存,以便可以使用这个语句句柄。这是由 Connection属性的 setter 方法完成的,如下列代码所示:
CliReturn ret;
IntPtr myConnectionHandle = ((Db2Connection) value).hConnection;
ret = CLIInvoke.SQLAllocHandle(CliHandleType.SQL_HANDLE_STMT,
myConnectionHandle, ref _hStatement);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot allocate statement handle");
|
我们通过 CLIInvoke包装器类来使用 SQLAllocHandleDB2 CLI 函数。注意,我们要通知该函数我们想要一个语句句柄。我们必须提供连接句柄作为一个参数。该连接句柄是从 _connection私有字段获得的 —— 这就是我们在 Connection属性的 setter 方法中为语句句柄分配内存的原因。
与往常一样,这里我们也要测试返回代码。
执行一条语句 我们将重点关注 ExecuteNonQuery语句,以便演示一条数据操纵语言的语句是如何在我们的数据提供程序中执行的。
int result = 0;
CliReturn ret;
if ((_connection == null) || (_connection.State !=
ConnectionState.Open))
throw new InvalidOperationException();
ret = CLIInvoke.SQLExecDirect(_hStatement, _commandText,
_commandText.Length);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot execute the command");
ret = CLIInvoke.SQLRowCount(_hStatement, ref result);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Cannot retrieve the number of
affected rows");
return result;
|
ExecuteNonQuery方法必须返回一个整型值(受该语句影响的行的数目),因此实现分为两步:
- 调用 SQLExecDirectDB2 CLI 函数来执行这条语句,
- 调用 SQLRowCount函数来获得受该语句影响的行的数目。
我们还要检查这些函数的返回代码。
释放语句句柄 执行了语句之后,我们必须释放提供程序所占有的相应资源(在我们的例子中就是句柄)。对于 Db2Connection类,我们将在 Db2Command 类的 Dispose 方法中释放语句句柄。
ret = CLIInvoke.SQLFreeHandle((ushort) CliHandleType.SQL_HANDLE_STMT,
_hStatement);
if ((ret != CliReturn.SQL_SUCCESS) && (ret !=
CliReturn.SQL_SUCCESS_WITH_INFO))
throw new Db2Exception("Error when trying to dispose the
statement handle");
|
我们通过 CLIInvoke 助手类来使用同样的 SQLFreeHandle DB2 CLI 函数,然后检查返回代码。
测试提供程序
这个提供程序既可以在 Linux 下编译和测试,也可以在 Windows 下编译和测试。我们可以使用 Visual Studio .NET 的 Microsoft C# 编译器来编译该提供程序,或者也可以从命令行编译:
csc /r:System.Data.dll /target:library /out:System.Db2Client.dll *.cs
|
用 mono 编译时几乎是一样的,只是编译器的名称换为 mcs。
结束语
您可以看到,为 DB2 UDB 系统实现一个托管数据提供程序这一任务可以用一种直接的方式来完成。我已经向您展示了一些基本的东西。当然,我所提供的代码只是 DB2 UDB 托管数据提供程序的不完整实现。为了简明扼要,这里我省略了一些本应该有的编程任务,例如错误处理、资源共享(resource pooling)、线程化(threading)等。本文并不打算讲述这些主题。
不管您是要编写自己的托管数据提供程序,还是只涉及 System.Data .NET 库的一些浅显的东西,我都希望本文对您有所帮助。DB2 UDB 数据提供程序将使得 .NET 应用程序(在 Windows 和其他操作系统上都可以 —— 这是开发源代码所带来的好处)可以利用 DB2 UDB 系统的强大性和稳定性。 |