OLEDB作为目前最全面,最强大的Windows平台下的数据库编程接口,其资料在网上却少之又少,这着实有些让人纳罕。
现在很多的应用软件系统都要和数据库打交道,没有一个好的强大的数据库编程接口作为支撑,这些系统的功能,性能,安全性等等都将是不可想象的事情。当然我们还可以选择OLEDB之上的ADO接口来作为我们的编程接口,由于ADO是基于OLEDB的上层封装,ADO比之OLEDB最大的优势就在于方便。使用ADO只需要3-5行代码的事情,用OLEDB却需要将近200-300行代码才能完成,这也是很多系统目前使用ADO作为数据库编程接口的主要原因。当然这并不表示OLEDB在功能上比ADO有什么逊色之处,相反,OLEDB接口不但功能强大,而且由于处于底层的原因,它在灵活性上有更明显的优势。甚至我鼓励VC++程序员在大型项目中使用OLEDB而不是ADO来作为数据库编程的接口,因为OLEDB的很多东西都是由你完全控制的,比如数据存放的位置,存放的方式等等。
另外OLEDB还可以作为数据提供者编程接口,比如你可以自己实现一个ORACLE或MySQL的OLEDB提供程序。
好了,废话就不多说了,下面就开始我们正式的OLEDB之旅,注意本文以及以后一系列文章中所有的关于OLEDB的介绍都以OLEDB2.6以上版本为准,你可以在微软的网站上免费下载到完整的OLEDB2.6SDK包,其中有关于OLEDB,ADO等的详细帮助以及例子代码。
首先让我们来看看OLEDB中两个最基本的概念:数据提供者和数据消费者。在OLEDB中,不但要考虑使用数据的一方,还要考虑提供数据的一方,比如各种数据库系统几乎都提供了自己的OLEDB接口。从本质上说,OLEDB其实就是一个标准的数据库与应用系统间的数据标准交换接口,它的好处就是高效,通用和灵活。
如果是数据提供的一方,在OLEDB中就称之为数据提供者,如果是使用数据的一方,那么就称之为数据消费者。在此系列文章中将主要的介绍一下数据消费者接口。
OLEDB顾名思义,它肯定是一组COM接口,因此要用好OLEDB,一个必不可少的先决条件就是必须熟悉COM原理,当然这对现在的VC++程序员来说是必须掌握的技能之一,本文中不对COM的基本内容做任何介绍,需要的请自己查阅相关资料。
要完成一个数据库访问任务,第一件要做的事情就是打开一个连接,在OLEDB2.0以前的版本中,要做到这点只需要像下面这样调用一段代码就可以创建一个数据库连接:
#define COM_NO_WINDOWS_H //如果已经包含了Windows.h或不使用其他Windows
//库函数时
#define DBINITCONSTANTS
#define INITGUID
#define OLEDBVER 0x0250
#include <oledb.h>
#include <oledberr.h>
IDBInitialize *pIDBInitialize = NULL;
CoCreateInstance(CLSID_MSDASQL, NULL, CLSCTX_INPROC_SERVER,
IID_IDBInitialize,(void**)&pIDBInitialize);
当然如果你使用的SQL Server 2005以上的数据库,那么还可以像下面这样来创建一个连接对象:
#include <sqlncli.h>
CoCreateInstance( CLSID_SQLNCLI10,NULL,CLSCTX_INPROC_SERVER,
IID_IDBInitialize,(void **) &pIDBInitialize);
这样调用的前提是你已经安装了SQL Server Native Client支持组件,当然这还需要你包含SQLNCLI.h这个头文件,这个在Windows SDK组件中找到它。
当然这只是创建了一个数据库连接的COM接口,而且要特别注意的是这是OLEDB2.0以前推荐的方法,现在不应该再这样使用了,介绍它的目的就是方便你看懂一些例子代码。
同时这个地方要特别注意的是,这种方法初始化出来的IDBInitialize接口,在后续的调用中是无法使用一些高级接口的,比如IRowsetFind、IRowsetIdentity等等,同时这将导致你后续创建的IRowset接口无法直接和ADO混用(ADO和OLEDB混用这个话题将在后续的文章中讨论),在2.0以后的OLEDB接口中,替代的是两个接口:IDataInitialize和IDBPromptInitialize。其中IDBPromptInitialize将打开那个Windows系统标准的数据库连接创建对话框,像下图这样:
而IDataInitialize则纯粹的是以代码的方式创建一个数据库连接对象。它们分别用于不同的编程目的。调用这两个接口例子如下:
1、使用IDataInitialize接口:
IDataInitialize* pIDataInitialize = NULL;
CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,
IID_IDataInitialize,(void**)&pIDataInitialize);
IDBInitialize *pIDBInitialize = NULL;
pIDataInitialize->CreateDBInstance(CLSID_MSDASQL, NULL,
CLSCTX_INPROC_SERVER, NULL, IID_IDBInitialize,
(IUnknown**)&pIDBInitialize);
2、使用IDBPromptInitialize接口:
IDBPromptInitialize* pIDBPromptInitialize = NULL;
CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER,
IID_IDBPromptInitialize, (void **)&pIDBPromptInitialize);
IDBInitialize *pIDBInitialize = NULL;
//下面这句将弹出前面所说的对话框
pIDBPromptInitialize->PromptDataSource(NULL, hWndParent,
DBPROMPTOPTIONS_PROPERTYSHEET, 0, NULL, NULL, IID_IDBInitialize,
(IUnknown **)&pIDBInitialize);
pIDBInitialize->Initialize();//根据对话框采集的参数连接到指定的数据库
上面两种方法中实际最后还是创建了IDBInitialize接口,表面上看这没什么区别,其实不然,在这个地方要特别注意的是,一定要使用后面这两种方法创建的IDBInitialize的接口,虽然很多例子中都像文中前面提到的那样直接创建了IDBInitialize接口,但是在正式的项目代码中一定要使用后面这两种方法之一来创建IDBInitialize接口,否则你就会遇到很多奇怪的问题,最重要的就是后面这两种方法打开了所有的数据源OLEDB功能支持,而直接创建的IDBInitialize只打开了基本OLEDB功能支持,这在项目中会导致很多问题,因为很多的接口以及属性都将无法使用。而后两种方法保证你后续所有的OLEDB接口和功能都能使用。
在创建了IDBInitialize接口之后(pIDBPromptInitialize->PromptDataSource这种方式创建的除外),我们就需要详细的指定连接数据库的各种参数,在OLEDB中这些参数都叫做属性,并且被加以分组,每个分组都叫做一个属性集合。典型的我们需要指定数据库实例名,数据库名,连接数据库的用户名以及密码等等属性。在SQL Server中有一种特殊的方式就是集成安全模式,它的属性相对简单些,下面的例子演示了如何使用集成安全性连接到SQL Server数据库:
DBPROP InitProperties[4];
DBPROPSET rgInitPropSet[1];
//初始化属性值变量
for ( i = 0 ; i < 4 ; i++ )
{
VariantInit(&InitProperties[i].vValue);
}
//指定数据库实例名,这里使用了别名local,指定本地默认实例
InitProperties[0].dwPropertyID = DBPROP_INIT_DATASOURCE;
InitProperties[0].vValue.vt = VT_BSTR;
InitProperties[0].vValue.bstrVal= SysAllocString(L"(local)");
InitProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[0].colid = DB_NULLID;
//指定数据库名
InitProperties[1].dwPropertyID = DBPROP_INIT_CATALOG;
InitProperties[1].vValue.vt = VT_BSTR;
InitProperties[1].vValue.bstrVal = SysAllocString(L"MyTest");
InitProperties[1].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[1].colid = DB_NULLID;
//指定身份验证方式为集成安全模式“SSPI”
InitProperties[2].dwPropertyID = DBPROP_AUTH_INTEGRATED;
InitProperties[2].vValue.vt = VT_BSTR;
InitProperties[2].vValue.bstrVal = SysAllocString(L"SSPI");
InitProperties[2].dwOptions = DBPROPOPTIONS_REQUIRED;
InitProperties[2].colid = DB_NULLID;
//创建一个GUID为DBPROPSET_DBINIT的属性集合,这也是初始化连接时需要的唯一一//个属性集合
rgInitPropSet[0].guidPropertySet = DBPROPSET_DBINIT;
rgInitPropSet[0].cProperties = 4;
rgInitPropSet[0].rgProperties = InitProperties;
//得到数据库初始化的属性接口
IDBProperties* pIDBProperties = NULL;
hr = pIDBInitialize->QueryInterface(IID_IDBProperties, (void **)&pIDBProperties);
if (FAILED(hr))
{//无法得到IDBProperties接口,详细的错误信息可以使用IerrorRecords接口得到
return FALSE;
}
hr = pIDBProperties->SetProperties(1, rgInitPropSet);
if (FAILED(hr))
{//设置属性失败
return -1;
}
//属性一但设置完成,相应的接口就可以释放了
pIDBProperties->Release();
//根据指定的属性连接到数据库
pIDBInitialize->Initialize();
在上面的代码中,新加入了一个重要的概念就是属性和属性集合,对于OLEDB来说,除了接口,就是属性和属性集合最重要了,在MSDN或SDK自带的帮助文档中都有关于每个属性以及属性集合的详细介绍,尤其是详细的说明了每个属性控制的接口行为,弄清楚这些OLEDB就没有什么秘密可言了,还有一点就是有些OLEDB提供者,如ORACLE的OLEDB提供者还添加了一些特殊的属性,这些属性往往就要看相应的提供者帮助文档才能知道,在MSDN中通常是没有这些特殊属性的任何有用信息的。
对于一个属性集合而言,最重要的就是它的标识,标识其实就是一个预定义的GUID值,在连接数据库时,最重要的属性集合就是DBPROPSET_DBINIT,在这个属性集合中有下列的属性:
DBPROP_AUTH_CACHE_AUTHINFO DBPROP_INIT_GENERALTIMEOUT
DBPROP_AUTH_ENCRYPT_PASSWORD DBPROP_INIT_HWND
DBPROP_AUTH_INTEGRATED DBPROP_INIT_IMPERSONATION_LEVEL
DBPROP_AUTH_MASK_PASSWORD DBPROP_INIT_LCID
DBPROP_AUTH_PASSWORD DBPROP_INIT_LOCATION
DBPROP_AUTH_PERSIST_ENCRYPTED DBPROP_INIT_LOCKOWNER
DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO DBPROP_INIT_MODE
DBPROP_AUTH_USERID DBPROP_INIT_OLEDBSERVICES
DBPROP_INIT_ASYNCH DBPROP_INIT_PROMPT
DBPROP_INIT_BINDFLAGS DBPROP_INIT_PROTECTION_LEVEL
DBPROP_INIT_CATALOG DBPROP_INIT_PROVIDERSTRING
DBPROP_INIT_DATASOURCE DBPROP_INIT_TIMEOUT
这些属性不一一赘述,你可以在MSDN2008中找到关于它们的详细介绍,这里只讲几个重要常用的属性:
DBPROP_INIT_DATASOURCE这个属性指定我们要连接的数据库实例名,它是一个字符串型属性,前面的例子中已经演示了如何设定这个属性,夸张的是这需要5行代码(不包括属性定义)。通常对于一个项目来说,最不固定的就是这个参数,因此建议这个参数放置到配置文件中,以方便连接到部署在不同地方的数据库。
DBPROP_INIT_CATALOG是第二个比较重要的属性,它将指定我们要连接的具体数据库的名称,注意它叫做CATALOG(目录),而不是数据库什么的,因此这是一个比较不好记住的属性。
如果我们没有指定DBPROP_AUTH_INTEGRATED这个属性,那么我们就需要提供连接数据库的用户名和密码,其中用户名使用DBPROP_AUTH_INTEGRATED属性指定,这也是一个字符串型的变量,而密码则使用DBPROP_AUTH_PASSWORD属性来指定。
在设置属性时要特别注意的就是,一个属性集合中的属性必须是连续存放的,也就是说,它们必须是一个数组,还有一个需要注意的就是那些通过调用SysAllocString函数得到字符串变量就不需要释放了,这个由SetProperties函数在内部负责释放。而不同属性集合的属性就可以放到不同的数组中。
在连接到一个数据源时我们还有一种方法就是使用连接字符串,这也是很多使用ADO的程序员常用的一种方式,在OLEDB中这可以通过IdataInitialize接口的GetDataSource方法来实现,例子如下:
IDataInitialize* pIDataInitialize = NULL;
CoCreateInstance(CLSID_MSDAINITIALIZE, NULL, CLSCTX_INPROC_SERVER,
IID_IDataInitialize,(void**)&pIDataInitialize);
IDBInitialize *pIDBInitialize = NULL;
//使用连接字符串得到一个IDBInitialize接口
pIDataInitialize->GetDataSource(NULL,
CLSCTX_INPROC_SERVER,
L"Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D://My Document//db1.mdb;Mode=Share Deny None;", __uuidof(IDBInitialize),
IUnknown**)&pIDBInitialize);
//连接到数据库
pIDBInitialize ->Initialize();