ADO 概览






4.70/5 (63投票s)
关于使用 ADO 的简单介绍。
引言
Microsoft ActiveX Data Objects (ADO) 允许您通过 OLEDB 提供程序在数据库服务器中操作和存储数据,并且由于它提供双接口,因此您可以使用 VBScript 和 JavaScript 等脚本语言以及 C++ 来使用它。它具有许多优点,例如高速、低内存开销以及最重要的一点,易用性。
在这篇简短的文章中,我将描述如何开始使用 ADO,例如,如何打开记录集、查询数据库或执行存储过程。
首先,让我告知您所需的要求。在本文中,我使用了 *ADOTestDB.MDB* 作为示例数据库,结合 VC++ 6 (SP3) 和 ADO 2.1。在数据库文件中,我实现了两个简单的表,名为 `Student` 和 `Dept`,它们具有不同类型的不同字段和一些示例数据(您可以从上面的链接下载)。我应该在这里提及的另一件事是,我在本文中使用了 `import` 指令并获得了智能指针的功能。
在您的 *stdafx.h* 中输入以下几行,您也可以轻松地做同样的事情。
#import "msado15.dll" \
no_namespace \
rename( "EOF", "adoEOF" )
如果您提及上述代码,您会发现我将 `EOF` 重命名为 `adoEOF`,这样做将防止您的应用程序将来出现一些讨厌的冲突,这些冲突会浪费您查找原因的时间。
完成此操作后,您需要先初始化 COM,然后才能执行任何操作。为此,在程序开头某处调用 `CoInitialize(NULL)` 函数,并在使用 ADO 完成工作后调用 `CoUninitialize()`。
现在,我们进入使用 ADO 的主要部分。
连接到数据库服务器
在 ADO 中访问数据源的第一步是连接到数据源。为了连接到数据源,您可以使用 ADO 的 `Connection` 对象。ADO 建立连接的主要方式是通过 Connections Open 成员函数。请看以下代码(为简洁起见,省略了错误处理)
_ConnectionPtr m_pConn;
m_pConn.CreateInstance (__uuidof(Connection));
m_pConn->Open (
_bstr_t ( "Provider=Microsoft.Jet.OLEDB.4.0;
Data Source = ADOTestDB.MDB" ),
_bstr_t ( "" ),
_bstr_t ( "" ),
adModeUnknown );
这是一个通过 `Microsoft.Jet.OLEDB.4` 提供程序建立与 *ADOTestDB.MDB* 连接的示例代码片段。首先,您必须定义一个 `_ConnectionPtr` 类型的对象 (ADO Connection),然后您应该实例化它,最后,调用 `Open` 函数。`Open` 成员函数的第一个参数是连接字符串。此参数指定您尝试连接的数据源及其 OLEDB 提供程序。接下来的两个参数指定登录到数据库的 `UserName` 和 `Password`。我们的示例数据库没有定义任何用户,因此它们可以是空 `string`,最后一个参数是 `Options`,它确定打开操作必须是同步函数还是异步函数。如果您不了解此功能,可以像我一样简单地键入 `adModeUnknown`。
值得一提的是,您也可以使用 ODBC 驱动程序连接到数据源,以下代码将向您展示如何操作
m_pConn->Open (_bstr_t ("DSN=pubs;uid=sa;pwd=;"),
_bstr_t (""),
_bstr_t (""),
adCmdUnknown );
表示 SQL 语句
在 ADO 中有不同的方式来表示 SQL 语句。最常见的方式是使用 `Command` 对象。`Command` 是数据提供程序理解的用于修改、管理和操作数据源的指令,通常用 SQL 编写。现在我们将查询我们的示例数据源中 `Student` 表中的所有学生。代码如下所示
_CommandPtr pCommand;
pCommand.CreateInstance (__uuidof (Command));
pCommand->ActiveConnection = m_pConn; // Formerly opened connection
// pointer
pCommand->CommandText = "Select * From Student";
如您所见,我们首先声明了一个 `Command` 对象,然后实例化了它。接下来,我们通过先前打开的连接对象 `m_pConn` 设置了命令对象的 `ActiveConnection` 属性。命令对象的 `CommandText` 属性表示将针对提供程序发出的查询、SQL 语句或存储过程名称,在我们的例子中,它是一个简单的查询语句,“`Select * From Student`”。
由于我们的 `command` 文本是一个查询,因此通过发出它,它将返回一组行,我们应该将其存储在某个地方。为此,我们将定义一个 `Recordset` 对象,该对象将存储返回的行,此外还将允许您操作这些行。
现在,我们要执行此命令,但让我像以前一样提及一些事情。在 ADO 中有两种执行命令的方式,第一种是通过 `Command` 对象的 `Execute` 成员函数,另一种是使用 `Recordset` 对象的 `Open` 成员函数。在这里,我们通过 `Recordset` 的 `Open` 来执行,如下所示
_RecordsetPtr pRecordset;
pRecordset.CreateInstance (__uuidof (Recordset));
pRecordset->CursorLocation = adUseClient;
pRecordset->Open ( (IDispatch *) pCommand, vtMissing, adOpenStatic,
adLockBatchOptimistic, adCmdUnknown);
在代码片段中,我们首先定义了一个 `Recordset` 类型的对象,然后实例化了它。ADO 中的 `Recordset` 对象有十几个用于不同目的的宝贵属性,我们这里使用的是 `CursorLocation`,它允许您指定游标的位置,通常在客户端和服务器端之间。完成这些操作后,您可以调用 `Open` 函数以针对数据源发出命令。`Open` 成员函数的第一个参数指定一个求值为 `Command` 对象、SQL 语句、表名、存储过程调用或称为 `Source` 的 URL 的变体。第二个参数是一个活动连接,是可选的。由于我们指定了我们的活动连接在 `Command` 对象中,因此无需再次指定它,只需忽略该参数即可(在 VC++ 中,当您需要指定一个类型为 `Variant` 的缺失参数时,请指定一个值为 `DISP_E_PARAMNOTFOUND` 且类型为 `VT_ERROR` 的 `_variant_t`。或者,指定等效的 `_variant_t` 常量 `vtMissing`,该常量由 `#import` 指令提供)。第三个和第四个参数是游标类型和锁定类型。指定 `adLockBatchOptimistic` 锁定类型是因为我们将在此处使用批处理,并且因为此处理需要*游标服务*,所以我们事先指定了游标位置。最后一个参数指示如果 `Source` 参数不是 `Command` 对象,则提供程序应如何评估它,但我们的*源*在此处恰好是一个命令对象,因此只需将 `adCmdUnknown` 作为最后一个参数。现在,在调用 `Open` 函数后,您将在 `Recordset` 对象中拥有所有学生信息。
操作数据
此时,我们将对数据进行一些更改。`Student` 表中的一个字段是 `SocialSecNo`,它显示每个 `student` 的社会安全号码,考虑到政府面临的一些问题,它必须将以“`45`”开头的社会安全号码更改为“`77`”之类的号码。因此,我们必须将所有以“`45`”开头的 `SocialSecNo` 更改为“`77`”。为此,我们过滤当前记录集中所有以“`45`”开头的 `SocialSecNo`。此外,我们将 `StudentNo` 字段设置为 `recordset` 中的 `index`,以提高排序和过滤性能。
在这里,您可能会认为做像下面这样的事情效率不高,而且在某种程度上是正确的,这取决于情况,但我在这里最重要的目的是介绍 ADO 的不同功能,所以不要考虑效率,尽情享受 ADO 的功能吧。
操作数据的代码如下所示
pRecordset->Fields->GetItem ("StudentNo")->Properties->
GetItem ("Optimize")->Value = VARIANT_TRUE;
pRecordset->Sort = "Name";
pRecordset->Filter = "SocialSecNo LIKE '45*'";
while (!pRecordset->GetadoEOF())
{
CString str = (char *) (_bstr_t) pRecordset->Fields->
GetItem("SocialSecNo")->Value;
str = "77" + str.Right(str.GetLength() - 2);
pRecordset->Fields->GetItem("SocialSecNo")->Value =
(_bstr_t) str;
pRecordset->MoveNext();
}
pRecordset->Filter = (long) adFilterNone;
在上面的代码中,我们首先将 `StudentNo` 字段的 `Optimize` 属性设置为使其在 `recordset` 中成为索引,然后我们按 `Name` 字段对 `recordset` 进行排序,并过滤出 `SocialSecNo` 以“`45`”开头的记录。在 `while` 循环中,我们只是简单地将 `SocialSecNo` 值更改为新值,并将当前位置移动到 `recordset` 中的下一条记录。当您将 `SocialSecNo` 更改为新值时,它不再符合筛选条件,因此在 `recordset` 中是不可见的。为了重新显示记录,我们应该在最后删除筛选器,这正是我在代码最后一行所做的。
更新数据
ADO 中更新数据通常有两种方法。第一种方法是立即更新,这意味着您在调用 `Update` 函数后立即直接对 `recordset` 进行更改,从而对数据源进行更改。但第二种方法被称为批处理模式更新。如果您使用 `adLockBatchOptimistic` 锁定类型打开 `recordset`,ADO 允许您以批处理模式更新更改。在批处理模式下,对记录的每次导航或对当前记录调用 `Update` 函数都会将更改保存到复制缓冲区中,并且只有在调用 `BatchUpdate` 函数后,更改才会传播到数据源。
在我们的示例中,我们使用批处理模式,因此您可以使用以下简单代码行传播更改
pRecordset->BatchUpdate (adAffectAll );
现在您已经学会了如何打开和查询一些数据,然后操作和更新它们。但是 ADO 中还有很多您必须了解的东西,其中最重要之一就是存储过程和参数化命令。
执行带输入参数的存储过程
调用存储过程就像打开一个新的记录集一样简单,就像我们之前在这篇文章中所做的那样。创建输入参数并为其赋值只需进行一些小的补充,我们将在下面的几行中讨论。ADO 中有两种传递参数的常用方法,第一种是通过 `Parameter` 对象,第二种是通过 `Refresh` 方法,但我总是更喜欢第一种方法,因为它比第二种方法具有性能优势。
在这里,我将为您描述第一种方法。以下代码行向您展示了如何使用 `Parameter` 对象设置输入参数并执行我们示例数据库中的 `Query1` 存储过程。
_CommandPtr pCommand;
_ParameterPtr pParam1, pParam2;
CString str("Ma%");
pCommand.CreateInstance(__uuidof(Command));
pCommand->ActiveConnection = m_pConn; //Its the connection pointer
//we opened formerly
pCommand->CommandText = "Query1";
pCommand->CommandType = adCmdStoredProc;
pParam1 = pCommand->CreateParameter ( _bstr_t ("DeptID"), adTinyInt,
adParamInput, sizeof (BYTE),
_variant_t ( long (11)));
pCommand->Parameters->Append ( pParam1);
pParam2 = pCommand->CreateParameter ( _bstr_t ("Name"), adVarChar,
adParamInput, str.GetLength (),
(_bstr_t) str);
pCommand->Parameters->Append ( pParam2);
_RecordsetPtr pRecordset;
pRecordset.CreateInstance(__uuidof(Recordset));
pRecordset = pCommand->Execute(NULL, NULL, adCmdStoredProc);
首先,创建一个 `Command` 对象,然后将 `ActiveConnection` 属性设置为已打开的连接,接下来,在 `CommandText` 中指定您的存储过程名称,并将 `adCmdStoredProc` 值分配给 `Command` 对象的 `CommandType` 属性。由于您的存储过程有两个输入参数,因此声明两个 `Parameter` 对象,然后通过调用 `Command` 对象的 `CreateParameter` 方法来创建它们。`CreateParameter` 的第一个参数指代参数的可选名称,第二个指定参数的类型,第三个确定参数的方向,在我们的例子中是输入。第四个参数表示变量的长度,第五个参数指定参数的值。
指定参数后,创建的参数将分别分配给 `pParam1` 和 `pParam2`。然后应该将创建的参数添加到(`Appended`)我们的 `Command` 对象的 `Parameters` 集合中,为此,我们为 `Parameters` 集合上的每个 `Parameter` 对象调用 `Append` 方法。现在,您的命令已准备好执行。
如果您提及我们示例数据库中的 `Query1` 存储过程,您会发现它是一个简单的查询语句,它将返回一个 `rowset` (`recordset`),因此在执行 `command` 对象后,它很可能会返回一个 `recordset`,我们可以像上面那样轻松地将其存储在 `Recordset` 对象中。
错误处理机制
到目前为止,本文中的所有代码片段都没有考虑错误处理,因为代码简洁。此时,我将提及一个简单的错误处理示例,您可以在程序中任何使用 ADO 关键函数的地方使用它。通常,由于 ADO 由 COM 对象组成,因此在运行时发生错误时会生成 `_com_error` 类型的异常。您可以通过将 ADO 函数调用放在 `try - catch` 块中来处理生成的异常。请看以下代码
try
{
HRESULT hr = m_pConn.CreateInstance(__uuidof(Connection));
if (FAILED( hr ))
AfxMessageBox(
"Can't create an intance of ADO.Connection" );
if (FAILED( m_pConn->Open(
_bstr_t(
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source =
ADOTestDB.MDB"), _bstr_t( "" ), _bstr_t( "" ),
adModeUnknown )))
AfxMessageBox( "Can't open datasource" );
...
...
...
m_pConn->Close();
}
catch( _com_error &e )
{
_bstr_t bstrSource(e.Source());
_bstr_t bstrDescription(e.Description());
TRACE( "Exception thrown for classes generated by #import" );
TRACE( "\tCode = %08lx\n", e.Error());
TRACE( "\tCode meaning = %s\n", e.ErrorMessage());
TRACE( "\tSource = %s\n", (LPCTSTR) bstrSource);
TRACE( "\tDescription = %s\n", (LPCTSTR) bstrDescription);
}
catch (...)
{
TRACE ( "*** Unhandled Exception ***" );
}
考虑在上述代码中,`Open` 函数调用无法在文件夹中找到指定的 mdb 文件,因此它将生成一个异常,确定错误类型和错误代码,以及对错误的简要描述。
注释
这是对 ADO 某些功能的简要解释。如果您想成为这方面的专家,ADO 中确实还有许多其他内容需要学习。在这里,我试图为您提供一些线索,让您开始学习 ADO 并使用它开发有用的应用程序,我也正在努力在不久的将来发布更多关于这个主题的文章,讨论更多有趣和独特的功能。
如果您有任何建议、推荐或问题,请随时提出。我很高兴能听到文章的缺点和优点。您可以通过电子邮件 shokuie@hotmail.com 与我联系。