Chinaunix首页 | 论坛 | 博客
  • 博客访问: 69357
  • 博文数量: 29
  • 博客积分: 1500
  • 博客等级: 上尉
  • 技术积分: 270
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-12 14:09
文章分类

全部博文(29)

文章存档

2017年(1)

2012年(1)

2011年(5)

2010年(2)

2009年(20)

我的朋友
最近访客

分类:

2009-11-13 11:00:45

     有人经常问:现在最好的数据访问方法是什么?回答当然是ADO!M$花了不少时间,推出了一种叫 UDA(Universal Data Access)的东东,还有一套看起来蛮简单的数据访问对象ADO(ActiveX Data Object),用来替代过时的 DAO(Data Access Object)、RDO(Remote Data Object)。

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文件里,需要加上下面的代码:

#import “c:\program files\common files\system\ado\msado15.dll”

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的预先字段绑定,Data Field Exchange。动态绑定即使用SQL语句动态构造结果字段,而不是象 CDAORecordset 里面用DFX去把所有原始字段映射成成员变量。动态绑定的例子:

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调用存储过程有多么容易!

 

 
 
 
 
 
阅读(694) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:List Control控件技巧总汇

给主人留下些什么吧!~~