分类:
2009-11-13 11:00:45
DAO 的底层是 JET 引擎,主要用来提供对 ACCESS 数据库的访问,比较新的版本也支持访问其他数据库,不过对于其他数据库,需经过 JET 的中间层,访问速度比较差。在所有对 ACCESS 数据库的访问方法中, JET是最快的。最新的 JET Engine版本为4.0,对应的 DAO 版本为3.6,可以访问 ACCESS 2000 的数据库。MFC里面 CDAODatabase和 CDAORecordset 即为 DAO 的 MFC 包装。
RDO 的底层是 ODBC,RDO仅仅是对ODBC API的一个薄包装层,薄得简直有点不象样很多人都用 ODBC API写过程序 (我的第一个SQL Server客户端程序就是用ODBC API写的一个Console Application),把那些并不复杂的API跟RDO一比就能发现,包装得简直没有专业精神。也正因为薄,所以速度较快,在ADO出现以前,访问MS SQL Server最快的方法就是 RDO 了,不要跟我说还有DB-Library,不论是看MS的文档,还是我亲自实验,DB-Library都没有ODBC/RDO快。最新的 RDO 版本为 2.0,说是最新,似乎也有好几年没有更新了,原因是MS早已经决定将其淘汰。不过遗憾的是,现在大家都还在用的 CDatabase、CRecordset 就是 RDO 的 MFC 包装。现在还用这两个类,就象有了宝马奔驰,还骑破永久上下班,只是因为不会开车。
ADO 的底层是 OLE DB,不仅能访问关系型数据库,也可以访问非关系型数据库,这可是现在最快速的数据库访问中间层啊!ADO对OLE DB的包装可以说相当成功,对象模型简明扼要,没有一点多余的东西,功能还远超DAO、RDO。直到此时,我才算是有点佩服MS,OLE DB里面数十个密密麻麻的接口对我来说实在是太恐怖了,还是乖乖的用ADO吧。
有一点很不幸,微软提供的ADO文档几乎没有有关VC的内容,象我这样的VC菜鸟,坐进了宝马舒适的驾驶仓,不知道该怎么上手下脚,郁闷之极!这篇文章真是救黎民于水火之中,不容我不把它贡献出来让大家共享。
开始:
在用ADO以前,一定得让你的程序知道去哪里找ADO。在stdafx.h文件里,需要加上下面的代码:
#imp
no_namspaces
rename(“EOF”,”adoEOF”)
这行代码的作用是,告诉编译器去哪里找ADO的库文件(可能在你的机器上路径有所不同),然后说明不用namespace,并且将 EOF 更名为 adoEOF(如果不这样干,很有可能会碰到常量冲突)。只要加了这句话,准备工作就全干完了,很简单是吗?不用包含任何头文件,不用为link指定任何lib文件,我也觉得有点神奇。
_ConnectionPtr, _CommandPtr, 和 _RecordsetPtr (本文中未提及 _CommandPtr):
ADO和 CDAODatabase、CDatabase 非常相似,也分这么几块,不同的是,ADO 以 COM 为基础,这几块都是标准的COM组件,而 CDatabase 等等则是 MFC 类。有一点必须提请注意,要学习ADO编程,学点COM是不可避免的了,不过这是件好事,现在如果不会一点COM、OLE什么的,估计很难适应Windows编程的形势。ADO里面的三个组成部份就是三个COM组件:Connection、Command、Recordset。(还有两个暂时用不上的:)
Connection用于建立数据库连接,执行不返回任何结果集的SQL语句。
Command用于返回结果集,并提供简单的方法执行存储过程或者任何返回结果集的SQL语句。
Recordset就是结果集,可进行数据的存取、滚动操作。
如果给Command和Recordset正确的Connection string,而不是一个Connection对象的指针,它们一样可以打开记录集,这种情况适用于单数据库操作。当程序里需要频繁进行数据库操作时,最好还是预先定义一个Connection对象,用它连接数据库,而用Recordset处理数据。本文中将大量讨论这两个对象。
_ConnectionPtr是一个Connection的接口,与CDatabase和CDAODatabase类似,实际工作原理也差不多。在程序里创建它的实例,通过某个OLE DB provider指向一个数据源,并开启连接。下面的代码是CDAODatabase和_ConnectionPtr的开启实例:
DAO:
CDaoDatabase MyDb = new CDaoDatabase();
m_DaoServerDB.Open(NULL,FALSE,FALSE,
”ODBC;DSN=SAMS_SVR;UID=admin;PWD=adin”);
ADO:
_ConnectionPtr MyDb;
MyDb.CreateInstance(__uuidof(Connection));
MyDb->Open(“DSN=SAMS_SVR;UID=admin;PWD=admin”,””,””, -1);
_RecordsetPtr是记录集接口,与CDAORecordset类似。先看看它的开启方式与CDAORecordset有多么相似:
DAO:
CDaoRecordset MySet = new CDaoRecordset(MyDb);
MySet->Open(AFX_DAO_USE_DEFAULT_TYPE, ”SELECT * FROM TableName”);
ADO:
_RecordsetPtr MySet;
MySet.CreateInstance(__uuidof(Recordset));
MySet->Open(“SELECT * FROM TableName”,
MyDb.GetInterfacePtr(), adpenDynamic, adLockOptimistic, adCmdText);
请注意:ADO在Open Recordset的时候,使用了MyDb.GetInterfacePtr()作为参数之一,当时我用了_ConnectionPtr, &(_ConnectionPtr)都不行,原来要这么用?真是不服不行。
ADO只是略微的麻烦一点,不过花这点功夫获得ADO的多多好处还是很值的。现在有了一个Connection和一个Recordset,下面该用这两个东东取点数据出来了。不妨先假定有一个名为m_List的Listbox,我们把数据取出来往里塞。
DAO:
VARIANT *vFieldValue;
COleVariant covFieldValue;
CString Holder;
while(!MySet->IsEOF())
{
MySet->GetFieldValue(“FieldName”, covFieldValue);
vFieldValue = (LPVARIANT)covFieldValue;
if(vFieldValue->vt!=VT_NULL)
{
Holder.Format(“%s”,vFieldValue->pbVal);
m_List.AddString(Holder);
}
MySet.MoveNext();
}
ADO:
_variant_t Holder
while(!MySet->adoEOF)
{
Holder = MySet->GetCollect(“FieldName”);
if(Holder.vt!=VT_NULL)
m_List.AddString((char*)_bstr_t(Holder));
MySet->MoveNext();
}
注意:在微软所有的文档里,都没找到GetCollect方法。我找了所有的地方,也从没人提起过,在文档里的示例方式是这样:
Holder = MySet->GetFields->Field->(_variant_t(FieldNumber))->Value;
你喜欢哪一种呢,反正我是喜欢GetCollect。
动态绑定 VS DFX:
DFX即CRecordset 和 CDAORecordset的预先字段绑定,Da
SELECT (SUM(field_1) + SUM(field_2)) AS answer FROM TableName
如果用DFX,估计就得在程序里自己写了:
m_answer = m_field_1 + m_field2;
译者注:尽管很多人喜欢 DFX 的字段预先绑定,宁可这么写代码,不过我很少用DFX,当然在 VB 里面更是从来不会用到,好象用得比较多的也就是在 Delphi 里面,用起来还比较爽,大概是 MS 的文档里让我别用,而 Borland 的破文档里却没说吧。相比之下,动态绑定确有其优越的地方,减少了代码量,也让程序更小、更易维护。再说,这也是MS推荐的获取数据方式,更灵活,速度更快,更易维护,我们还能要求什么呢?
对于大多数程序来说,都是创建一个全局的Connection,然后用Recordset来处理数据,如果Recordset数量很多,可以想象光是花费在DFX上面的代码就有多少。如果使用动态绑定,这些都省掉了。
_variant_t 和_bstr_t
很不幸,我们喜爱的CString类在COM里用不了(CStringEx也一样),因为COM必须设计成跨平台,它需要一种更普遍的方式来处理字符串以及其他数据。这就是VARIANT数据类型的来历,还有BSTR类型。VARIANT就是一个巨大的 union,包含了你能想得到的所有的数据类型,除了char *,不过还好,BSTR取代了char *。
译者注:似乎VARIANT是个很慢的东西,大家都不愿意使它,不过按我看来,情况没这么糟糕,union照理说不应该慢到哪去,要说慢,也是慢在给VARIANT分配地址空间上,这点在VC里面做得比VB要好。这些东西看起来的确有点恐怖,不过实在用不着怕,等下面熟悉了这两个东西之后,你会很快喜欢的。
简单来说,_variant_t是一个类,包装了VARIANT数据类型,并允许我们简单的对之进行强制类型转换,_bstr_t对BSTR干了同样的事情。在下面的例子里,你将看到怎么用GetCollect把数据取到VARIANT里,又怎么把它放到_bstr_t里,最后强制转换成char *,以及把_variant_t强制转换成long、double或者其他一切东西:
_variant_t Holder;
// first get the VARIANT and put it into the _variant_t
Holder = MySet->GetCollect(“FIELD_1”);
// now put it into a _bstr_t and cast it to a char*
m_List.AddString((char*)_bstr_t(Holder));
对比一下没有用 _variant_t 和 _bstr_t 的代码:
COleVariant covFieldValuel
VARIANT vFieldValue
CString Holder;
MySet->GetFieldValue(“FIELD_1”, covFieldValue);
vFieldValue = (LPVARIANT)covFieldValue;
Holder.Format(“%s”,vFieldValue->pbVal);
m_List.AddString(Holder);
区别大了!
Update,Insert,Delete:
当我进行Update,Insert,Delete操作时,通常我喜欢用Connection和Command对象,原因是,用CString来构造SQL语句简单一些,然后直接用Connection.Execute就行了。当然,用Recordset干这些也是可以的。
1. Update方法有下面三种方法调用:
a) 给某个Field对象(或某些个)的Value属性赋值,然后调用Update方法;
b) 将字段名和字段值作为参数传给Update方法;
c) 将字段名和字段值的数组作为参数传给Update方法。
2. AddNew方法如下调用:
a) 直接调用,然后同Update调用方法一
b) 将字段名数组、字段值数组作为参数传给AddNew方法。
3. Delete方法最简单,直接调用就行了,删除当前记录!
做完这些事情,可能需要调用Requery方法才能看到效果。
下面的示例代码需要我们创建一个简单的MFC Application,然后在CWinApp类里面声明三个接口的对象:
// Global ADO Objects
_ConnectionPtr m_pConnection;
_CommandPtr m_pCommand;
_RecordsetPtr m_pRecordset;
在VC6里面有一点很有意思,如果敲 “m_pConnection.”,你会看到一个方法属性列表,如果敲”m_pConnection->“,还会看到一个方法和属性列表,当然里面的容完全不同,因为你实际是在指向两个不同的东西。下面就是这两种混用的代码:
_ConnectionPtr MyDb;
MyDb.CreateInstance(__uuidof(Connection));
MyDb->Open(“DSN=SAMS_SVR;UID=admin;PWD=admin””,””,””,-1);
回到示例代码,在 application 的 InitInstance 方法里,我打开数据连接,指向我机器上的一个数据库,你需要更改ConnectionString,使用你自己的ODBC数据源或指定一个OLE DB provider。
// When we open the application we will open the ADO connection
m_pConnection.CreateInstance(__uuidof(Connection));
m_pConnection->Open(“DSN=ADOTest”,”“,”“,-1);
如果你打开about对话框,就会看到一个Listbox,还有一个叫button1的按钮,这里面包含了 ADO 调用的核心代码。我创建了一个_RecordsetPtr接口的实例,打开我需要的记录集,然后遍历所有记录,将它们塞到Listbox里去:
_variant_t TheValue;
theApp.m_pRecordset.CreateInstance(__uuidof(Recordset));
try {
theApp.m_pRecordset->Open(
“SELECT DISTINCT FLDESC FROM tblFALines”,
theApp.m_pConnection.GetInterfacePtr(),
adOpenDynamic,adLockOptimistic, adCmdText);
while(!theApp.m_pRecordset->adoEOF)
{
TheValue = theApp.m_pRecordset->GetCollect(“FLDESC”);
if(TheValue.vt!=VT_NULL)
m_List.AddString((char*)_bstr_t(TheValue));
theApp.m_pRecordset->MoveNext();
}
theApp.m_pRecordset->Close();
}
catch(_com_error *e)
{…}
catch(...)
{…}
记得一定要用try和catch,否则ADO调用错误有可能使你的程序崩溃,一定要随时记得捕捉_com_error例外以及其它错误。
我尽可能的使代码简单,所以省略了很多细节,尤其是忽略了很多好的编程习惯(比如检查大多数COM方法都返回的HRESULT值)。本文的目的是想说,ADO并没什么难的COM也一样,而不是想表现ADO能做的所有事情。我甚至都没仔细想过ADO能为你带来什么,不过我肯定一点,那就是ADO比DAO、RDO更快、更容易使用、并且功能强大得多。看看本站点其它的文章,你就会知道,通过ADO调用存储过程有多么容易!