使用 Visual C++ 和 OLE DB 进行数据库开发:建立连接






4.98/5 (48投票s)
2005年4月29日
17分钟阅读

300179
如何使用 ATL OLE DB 消费者类启动连接,以及如何获取会话,以便使用该会话查询或编辑数据库。
引言
在 Windows 编程的许多领域,初始化或建立连接都是一种常见的模式。如果您想在窗口上绘图,则需要先通过调用 GetDC
或 CreateDC
来获取设备上下文,完成后再通过调用 ReleaseDC
或 DeleteDC
来关闭它。如果您想使用 Windows 套接字启动客户端和服务器之间的通信,则首先需要建立连接,完成后再关闭它。类似地,如果您想查询数据库以获取某些记录,则需要建立连接,完成后再关闭它。在本文中,我将向您展示如何使用 ATL OLE DB 消费者类建立连接,以及如何获取会话,以便使用该会话查询或编辑数据库。
要使用 OLE DB 提供程序初始化到数据库的连接,您需要两个 ATL 类:CDataSource
和 CSession
。要使用它们,请包含 atldbcli.h 文件。
#include <atldbcli.h>
为什么是两个类?因为您可以在同一个连接上创建多个会话。一旦使用 Open
或 OpenFromInitializationString
方法打开了数据源,您很可能也想启动一个会话。通过此会话,您可以创建事务并查询或编辑数据库。即使在使用模式类时,实际上也需要这样做。OpenFromInitializationString
方法只有一个重载,而 Open
方法有九个重载。
HRESULT OpenFromInitializationString(LPCOLESTR szInitializationString);
我将解释 Open
方法的所有重载,但主要使用第一个,以防我需要显示“数据链接属性”对话框,允许用户选择 OLE DB 提供程序及其连接属性。
HRESULT Open(HWND hWnd = GetActiveWindow(), DBPROMPTOPTIONS dwPromptOptions = DBPROMPTOPTIONS_WIZARDSHEET);
HRESULT Open(const CLSID& clsid, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
HRESULT Open(const CLSID& clsid, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
HRESULT Open(LPCSTR szProgID, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
HRESULT Open(LPCWSTR szProgID, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
HRESULT Open(LPCWSTR szProgID, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
HRESULT Open(LPCSTR szProgID, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
HRESULT Open(const CEnumerator& enumerator, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
HRESULT Open(const CEnumerator& enumerator, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
请注意,ATL 消费者类基于 COM,这意味着
- 我们必须在调用这些类之前调用
CoInitialize
,可能在InitInstance
中,然后在程序结束时在ExitInstance
中调用CoUninitialize
。 - 我们将需要处理
HRESULT
返回值,以指示成功或失败。
使用 CEnumerator 类枚举 OLE DB 提供程序
CEnumerator oProviders; HRESULT hr = oProviders.Open( ); if(SUCCEEDED(hr)) { // The following macro is to initialize // the conversion routines USES_CONVERSION; while(oProviders.MoveNext( ) == S_OK) { // Now you have the provider name // in oProviders.m_szName and description // in oProviders.m_szDescription #ifdef _UNICODE TRACE(oProviders.m_szName); TRACE(L"\n"); #else TRACE(W2A(oProviders.m_szName)); TRACE("\n"); #endif } oProviders.Close( ); }
CEnumerator
类提供了枚举系统中所有已安装 OLE DB 提供程序的方法。它是一种方便的方式,可以为用户提供自定义界面来选择提供程序及其连接属性。如果您需要使用标准对话框枚举 OLE DB 提供程序,只需调用 CDataSource
类的 Open
方法,不带任何参数。
hr = ds.Open( );
标准 OLE DB 数据链接属性对话框
请注意,CEnumerator
的 m_szName
通常是提供程序的 ProgID,可以在 Open
方法的多个重载中使用它来打开连接。
该代码由于使用了 #ifdef
,因此在应用程序的 Unicode 和 ANSI 生成中都能正常工作。USES_CONVERSION
宏用于初始化几个转换例程,例如将 ANSI 字符串转换为 Unicode 的 A2W
,以及反之亦然的 W2A
。m_szName
是一个宽字符串指针,可以直接用于 Unicode 函数,但在单字节生成中工作时,我们必须使用 W2A
例程将其转换为 ANSI。
上面示例中描述的算法是 ATL 消费者类的典型方式。我们首先声明一个类,该类实际上包含一个访问器,其中包含几个数据成员,例如 m_szName
和 m_szDescription
。当我们调用 MoveNext
方法时,这些数据成员将被填充下一个可用行的相应值,直到 MoveNext
返回 DB_S_ENDOFROWSET
并退出循环。当我们开始从数据库检索记录时,会发现这种模式很常见。
描述 | ProgID | ClassID | 数据库 |
IBM OLE DB Provider for DB2 | IBMDADB2.1 | {1E29B6C3-8EC6-11D2-AF46-000629B3CD56} | DB2 |
Microsoft Jet 4.0 OLE DB Provider | Microsoft.Jet.OLEDB.4.0 | {DEE35070-506B-11CF-B1AA-00AA00B8DE95} | Microsoft Access |
Microsoft OLE DB Provider for SQL Server | SQLOLEDB | {0C7FF16C-38E3-11d0-97AB-00C04FC2AD98} | Microsoft SQL Server |
MySQL.OLEDB Provider | MySQLProv | {C86FB69E-3664-11D2-A112-00104BD15372} | MySQL |
Oracle Provider for OLE DB | OraOLEDB.Oracle | {3F63C36E-51A3-11D2-BB7D-00C04FA30080} | Oracle |
Microsoft OLE DB Provider for Oracle | MSDAORA | {E8CC4CBE-FDFF-11D0-B865-00A0C9081C1D} | Oracle |
有关上述提供程序的 ProgID 和 ClassID 的 typedef
,请参阅随附磁盘上的 ProvDefs.h。
使用 DBPROPSET 结构和 CDBPropSet 类
DBPROPSET
结构用于传递有关连接的某些属性,以及稍后我们将看到的检索到的行集的属性。DBPROPSET
结构包含一个 DBPROP
数组,这些 DBPROP
类似于所需的属性。要使用该结构,我们首先创建一个 DBPROP
数组并为其赋值。然后,我们将此数组附加到 DBPROPSET
结构。如果属性属于不同的属性集,则我们将相同的属性集组合到一个 DBPROPSET
中。最后,我们将得到一个属性集数组。在 CDataSource::Open
方法的几个重载中,我们看到了指向 DBPROPSET
结构的指针。该结构用连接属性填充,并与属性集计数一起传递。以下代码片段阐明了我们的观点:
// the macro for conversions USES_CONVERSION; // build the property array of 3 properties DBPROP rgProperties[3]; // property 1: the datasource rgProperties[0].colid = DB_NULLID; rgProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties[0].dwPropertyID = DBPROP_INIT_DATASOURCE; rgProperties[0].dwStatus = 0; rgProperties[0].vValue.vt = VT_BSTR; rgProperties[0].vValue.bstrVal = ::SysAllocString(W2COLE(L"server_name")); // property 2: the database name rgProperties[1].colid = DB_NULLID; rgProperties[1].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties[1].dwPropertyID = DBPROP_INIT_CATALOG; rgProperties[1].dwStatus = 0; rgProperties[1].vValue.vt = VT_BSTR; rgProperties[1].vValue.bstrVal = ::SysAllocString(W2COLE(L"Northwind")); // property 3: the user id rgProperties[2].colid = DB_NULLID; rgProperties[2].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties[2].dwPropertyID = DBPROP_AUTH_USERID; rgProperties[2].dwStatus = 0; rgProperties[2].vValue.vt = VT_BSTR; rgProperties[2].vValue.bstrVal = ::SysAllocString(W2COLE(L"sa")); // combine the 3 properties into a property set structure DBPROPSET rgPropertySet[1]; rgPropertySet[0].cProperties = 3; rgPropertySet[0].guidPropertySet = DBRGPROPERTIESET_DBINIT; rgPropertySet[0].rgProperties = rgProperties; // now open the SQL Server database connection HRESULT hr = m_ds.Open(L"SQLOLEDB", rgPropertySet, 1); if(SUCCEEDED(hr)) m_ds.Close();
指定 DBPROPOPTIONS_REQUIRED
是为了指示该属性对于建立连接是必需的。如果不是,提供程序将返回错误并将 dwStatus
设置为 DBPROPSTATUS_NOTSUPPORTED
。dwOptions
的另一个值是 DBPROPOPTIONS_OPTIONAL
,它表示此属性是可选的,并且如果属性不满足,提供程序不应失败。
我们可以有一个两个属性集的数组而不是一个。在每个项中,我们定义一组相关属性,如下所示:
// the macro for conversions USES_CONVERSION; // build the property array of 3 properties DBPROP rgProperties1[3]; // property 1: the datasource rgProperties1[0].colid = DB_NULLID; rgProperties1[0].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties1[0].dwPropertyID = DBPROP_INIT_DATASOURCE; rgProperties1[0].dwStatus = 0; rgProperties1[0].vValue.vt = VT_BSTR; rgProperties1[0].vValue.bstrVal = ::SysAllocString(W2COLE(L"server_name")); // property 2: the database name rgProperties1[1].colid = DB_NULLID; rgProperties1[1].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties1[1].dwPropertyID = DBPROP_INIT_CATALOG; rgProperties1[1].dwStatus = 0; rgProperties1[1].vValue.vt = VT_BSTR; rgProperties1[1].vValue.bstrVal = ::SysAllocString(W2COLE(L"Northwind")); // property 3: the user id rgProperties1[2].colid = DB_NULLID; rgProperties1[2].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties1[2].dwPropertyID = DBPROP_AUTH_USERID; rgProperties1[2].dwStatus = 0; rgProperties1[2].vValue.vt = VT_BSTR; rgProperties1[2].vValue.bstrVal = ::SysAllocString(W2COLE(L"sa")); // build another property array of 1 property DBPROP rgProperties2[1]; // Property 1: application name rgProperties2[0].colid = DB_NULLID; rgProperties2[0].dwOptions = DBPROPOPTIONS_REQUIRED; rgProperties2[0].dwPropertyID = SSPROP_INIT_APPNAME; rgProperties2[0].dwStatus = 0; rgProperties2[0].vValue.vt = VT_BSTR; rgProperties2[0].vValue.bstrVal = ::SysAllocString(W2COLE(L"my application title")); // combine the 3 properties into a property set structure DBPROPSET rgPropertySet[2]; rgPropertySet[0].cProperties = 3; rgPropertySet[0].guidPropertySet = DBPROPSET_DBINIT; rgPropertySet[0].rgProperties = rgProperties1; rgPropertySet[1].cProperties = 1; rgPropertySet[1].guidPropertySet = DBPROPSET_SQLSERVERDBINIT; rgPropertySet[1].rgProperties = rgProperties2; // now open the SQL Server database connection HRESULT hr = m_ds.Open(L"SQLOLEDB", rgPropertySet, 2); if(SUCCEEDED(hr)) m_ds.Close();
这个应用程序名称属性有什么用?在调用 m_ds.Close
方法之前,打开“master”数据库并查找“sysprocesses”表。打开该表,您将在 program_name
字段中找到“my application title”。这意味着它是当前打开 SQL Server 数据库的进程之一。
以前示例中属性的设置可能看起来有点复杂。我不喜欢以这种方式处理结构。因此,我将介绍一个 ATL 类,它可以缩短处理属性和属性集的学习曲线。这个类是 CDBPropSet
。看看使用这个类时上面的代码会是什么样子:
CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, L"server_name"); rgPropertySet[0].AddProperty(DBPROP_INIT_CATALOG, L"Northwind"); rgPropertySet[0].AddProperty(DBPROP_AUTH_USERID, L"sa"); hr = m_ds.Open("SQLOLEDB", rgPropertySet, 1);
就是这样。AddProperty
方法负责添加不同类型的属性并对其进行初始化。传递给类的指针与传递给 DBPROPSET
结构的指针相同,因为该类是从该结构派生的。
与连接相关的属性
有许多与连接相关的属性。我不会在此全部列出,但要获取完整列表,请参阅 MSDN 库。下表显示了对我们最重要的属性。这些属性部分或全部适用于大多数关系数据库。
为了避免浏览器滚动,一些术语已被连字符 (-) 分开。
属性 | 属性集 | 类型 | 适用对象 |
DBPROP_INIT_DATASOURCE |
DBPROPSET_DBINIT |
VT_BSTR |
|
DBPROP_INIT_CATALOG |
DBPROPSET_DBINIT |
VT_BSTR |
|
DBPROP_AUTH_USERID |
DBPROPSET_DBINIT |
VT_BSTR |
|
DBPROP_AUTH_PASSWORD |
DBPROPSET_DBINIT |
VT_BSTR |
|
DBPROP_AUTH_INTEGRATED |
DBPROPSET_DBINIT |
VT_BSTR |
|
DBPROP_AUTH_MASK_PASSWORD |
DBPROPSET_DBINIT |
VT_BOOL |
|
DBPROP_AU-TH_ENCRYPT_PASSWORD |
DBPROPSET_DBINIT |
VT_BOOL |
|
DBPROP_INIT_ASYNCH |
DBPROPSET_DBINIT |
VT_I4 |
|
DBPROP_INIT_GENERALTIMEOUT |
DBPROPSET_DBINIT |
VT_I4 |
|
DBPROP_INIT_LOCATION |
DBPROPSET_DBINIT |
VT_BSTR |
|
DBPROP_INIT_MODE |
DBPROPSET_DBINIT |
VT_I4 |
|
DBPROP_INIT_HWND |
DBPROPSET_DBINIT |
VT_I8 |
|
DBPROP_INIT_PROMPT |
DBPROPSET_DBINIT |
VT_I2 |
|
DBPROP_INIT_TIMEOUT |
DBPROPSET_DBINIT |
VT_I4 |
|
DBPROP_INIT_DATASOURCE
服务器名称或数据库名称。如果是服务器名称,则
DBPROP_INIT_CATALOG
包含数据库名称。DBPROP_INIT_CATALOG
在
DBPROP_INIT_DATASOURCE
包含服务器名称的情况下,数据库名称。DBPROP_AUTH_USERID
登录用户 ID。
DBPROP_AUTH_PASSWORD
登录密码。
DBPROP_AUTH_INTEGRATED
指示使用的安全系统,无论是用户 ID-密码还是适用于使用当前登录用户凭据的 Windows 安全系统的 SSPI。
DBPROP_AUTH_MASK_PASSWORD
以屏蔽形式将密码发送给提供程序。
DBPROP_AUTH_ENCRYPT_PASSWORD
以加密形式将密码发送给提供程序。
DBPROP_INIT_ASYNC
指示连接是同步还是异步的。如果是异步的,
Open
方法将立即返回,并在后台执行连接。在同步模式下,打开尝试不会返回,直到成功或失败。DBPROP_INIT_GENERALTIMEOUT
获取行集和执行命令时使用的默认超时周期。
DBPROP_INIT_LOCATION
数据库的位置。
DBPROP_INIT_MODE
打开的数据库的共享模式。这通常适用于 Microsoft Access 等文件数据库。
DBPROP_INIT_HWND
提示对话框父窗口的窗口句柄。
DBPROP_INIT_PROMPT
指示是否提示用户提供缺失的连接信息。
DBPROP_INIT_TIMEOUT
数据库连接尝试的超时周期(以秒为单位)。如果此属性中指定的时间到期且连接尝试仍在进行中,则
Open
方法将返回不成功。
CDataSource::Open 方法
既然我们已经描述了建立连接的所有组件,现在我们可以描述 CDataSource::Open
方法的重载了。我将列出每个重载的使用代码示例,但每次都使用不同的数据库类型。OLE DB 是一个强大的框架,因为一旦您打开到所选数据库的连接,您就可以以相似的方式查询它或执行命令。
HRESULT Open(HWND hWnd = GetActiveWindow(), DBPROMPTOPTIONS dwPromptOptions = DBPROMPTOPTIONS_WIZARDSHEET);
这是唯一允许您打开标准“数据链接属性”对话框以可视化指定连接参数(包括提供程序)的方法。
hWnd
是标准对话框父窗口的窗口句柄,默认为当前活动窗口。第二个参数用于设置对话框的样式。默认值为DBPROMPTOPTIONS_WIZARDSHEET
,它适用于向导样式的属性页。您可以指定DBPROMPTOPTIONS_PROPERTYSHEET
用于属性表样式。HRESULT hr = m_ds.Open( ); if(SUCCEEDED(hr)) m_ds.Close( );
其余的重载是参数组合的混合。这些参数是:
clsid
:要使用的提供程序的 ClassIDpPropSet
:属性集数组nPropertySets
:属性集数组计数pName
:服务器名称或数据库名称pUserName
:登录用户 IDpPassword
:登录密码nInitMode
:Microsoft Access 等文件数据库的共享模式szProgID
:要使用的提供程序的 ProgIDenumerator
:用于指定提供程序的CEnumerator
类
HRESULT Open(const CLSID& clsid, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
// Open Microsoft SQL Server database named "Northwind" installed on a // server name "server_name" using the currently Windows logged-in user // credentials CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, L"server_name"); rgPropertySet[0].AddProperty(DBPROP_INIT_CATALOG, L"Northwind"); rgPropertySet[0].AddProperty(DBPROP_AUTH_INTEGRATED, L"SSPI"); CLSID clsid = {0xc7ff16cL,0x38e3,0x11d0, {0x97,0xab,0x0,0xc0,0x4f,0xc2,0xad,0x98}}; hr = m_ds.Open(clsid, rgPropertySet, 1);
HRESULT Open(const CLSID& clsid, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
// Open a Microsoft Access database with // path C:\Program Files\Microsoft // Office\Office10\Samples\Northwind.mdb exclusively CLSID clsid = {0xdee35070L,0x506b,0x11cf, {0xb1,0xaa,0x0,0xaa,0x0,0xb8,0xde,0x95}}; hr = m_ds.Open(clsid, _T("C:\\Program Files\\Microsoft Office" "\\Office10\\Samples\\Northwind.mdb"), NULL, NULL, DB_MODE_SHARE_EXCLUSIVE);
HRESULT Open(LPCSTR szProgID, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
// Open an Oracle database using Microsoft OLE DB Provider for Oracle // Open OraDB1 database with user name SYS and password // change_on_install // Note that it is using the ANSI string for the provider CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, L"OraDB1"); rgPropertySet[0].AddProperty(DBPROP_AUTH_USERID, L"SYS"); rgPropertySet[0].AddProperty(DBPROP_AUTH_PASSWORD, L"change_on_install"); hr = m_ds.Open("MSDAORA", rgPropertySet, 1);
HRESULT Open(LPCWSTR szProgID, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
// Open an Oracle database using Oracle Provider for OLE DB // Open OraDB1 database with user name SYS and password // change_on_install // Note that it is using the UNICODE string for the provider ProgID hr = m_ds.Open(L"OraOLEDB.Oracle", _T("OraDB1"), _T("SYS"), _T("change_on_install"));
HRESULT Open(LPCWSTR szProgID, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
// Open an MySQL database using a location "localhost", a database // name of "mysql", a user id of "root" CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_LOCATION, L"localhost"); rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, L"mysql"); rgPropertySet[0].AddProperty(DBPROP_AUTH_USERID, L"root"); hr = m_ds.Open("MySQLProv", rgPropertySet, 1);
HRESULT Open(LPCSTR szProgID, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
// Open a DB2 database named TOOLSDB with dbadmin user id and password hr = m_ds.Open("IBMDADB2.1", "TOOLSDB", "dbadmin", "dbadmin");
HRESULT Open(const CEnumerator& enumerator, DBPROPSET* pPropSet = NULL, ULONG nPropertySets=1);
// Open Northwind SQL Server database in "server_name" using SSPI // Integrated security CEnumerator oProviders; CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, L"server_name"); rgPropertySet[0].AddProperty(DBPROP_INIT_CATALOG, L"Northwind"); rgPropertySet[0].AddProperty(DBPROP_AUTH_INTEGRATED, L"SSPI"); hr = oProviders.Open( ); if(SUCCEEDED(hr)) { // The following macro is to initialize the conversion routines USES_CONVERSION; while((hr = oProviders.MoveNext( )) == S_OK) { #ifdef _UNICODE if(lstrcmpi(oProviders.m_szName, L"SQLOLEDB") == 0) #else if(lstcmpi(W2A(oProviders.m_szName), "SQLOLEDB") == 0) #endif { hr = m_ds.Open(oProviders, rgPropertySet, 1); break; } } oProviders.Close( ); }
HRESULT Open(const CEnumerator& enumerator, LPCTSTR pName, LPCTSTR pUserName = NULL, LPCTSTR pPassword = NULL, long nInitMode = 0);
// Open Northwind Access database exclusively CEnumerator oProviders; hr = oProviders.Open( ); if(SUCCEEDED(hr)) { // The following macro is to initialize // the conversion routines USES_CONVERSION; while((hr = oProviders.MoveNext( )) == S_OK) { #ifdef _UNICODE if(lstrcmpi(oProviders.m_szName, L" Microsoft.Jet.OLEDB.4.0") == 0) #else if(lstcmpi(W2A(oProviders.m_szName), " Microsoft.Jet.OLEDB.4.0") == 0) #endif { hr = m_ds.Open(oProviders, T("C:\\Program Files\\" "Microsoft Office\\Office10\\Samples\\Northwind.mdb"), NULL, NULL, DB_MODE_SHARE_EXCLUSIVE); break; } } oProviders.Close( ); }
我特意逐一说明了每个重载,以演示连接到不同类型的数据库。
OpenFromInitializationString 方法
HRESULT OpenFromInitializationString(LPCOLESTR szInitializationString, bool fPromptForInfo = false);
第一个参数是连接字符串,它指定执行连接的所有属性,包括提供程序 ProgID。该字符串应以宽格式提供。可以使用 A2W
和 W2COLE
函数来获取 ANSI 字符串的宽格式。第二个参数指示是否提示用户提供缺失的信息。默认情况下不提示,连接尝试将失败。连接字符串必须遵循特定的格式,其中键值对用分号分隔。键是属性名,值是其值。下表显示了属性名及其对应的属性 ID:
属性 ID | 属性关键字 |
提供程序 ProgID |
“Provider ” |
DBPROP_INIT_DATASOURCE |
“Data Source ” |
DBPROP_INIT_LOCATION |
“Location ” |
DBPROP_INIT_CATALOG |
“Initial Catalog ” |
DBPROP_AUTH_USERID |
“User Id ” |
DBPROP_AUTH_PASSWORD |
“Password ” |
DBPROP_AUTH_INTEGRATED |
“Integrated Security ” |
DBPROP_INIT_HWND |
“Window Handle ” |
DBPROP_INIT_PROMPT |
“Prompt ” |
DBPROP_INIT_TIMEOUT |
“Connect Timeout ” |
以下显示了正确的连接字符串:
// Open an Oracle database using Microsoft Provider
Provider=MSDAORA;Data Source=OraDB1;User Id=SYS;Password=change_on_install
// Open an MySQL database
Provider=MySQLProv;Location=localhost;Data Source=mysql;User Id=root
// Open a Microsoft Access database
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files\
Microsoft Office\Office 10\Samples\Northwind.mdb
到目前为止,我们已经了解了如何打开数据库。要关闭它,只需调用 CDataSource
的 Close
方法。打开数据库是一项耗时的任务,因此,除非您特别需要在查询后关闭连接,否则您应该在程序加载时打开数据库,并在主窗口或应用程序类的 CDataSource
和 CSession
成员中创建它们,并在整个应用程序中使用此相同连接来查询或编辑数据库。
CSession 类
现在,当我们打开了连接,我们就可以创建一个会话以便与数据库进行交互了。CSession
类处理会话的打开和事务的创建。要打开会话,请使用 CSession::Open
方法。
HRESULT Open(const CDataSource& ds);
它接受一个参数,该参数是对已打开数据源的引用。例如:
CDataSource ds; CSession session; HRESULT hr = ds.OpenFromInitializationString(L"Provider=MySQLProv;" "Location=localhost;Data Source=mysql;User Id=root"); if(SUCCEEDED(hr)) { hr = session.Open(ds); if(SUCCEEDED(hr)) { // The session is now open // use it to query or edit the database session.Close(); } ds.Close(); }
如示例所示,应调用 Close
方法来关闭会话。
会话最重要的特点是能够创建事务。事务是指在数据库上执行的一个或多个命令集,必须要么提交所有命令,要么丢弃所有命令。例如,如果我们想更改 Orders 表,然后在历史记录日志表中添加一个条目,那么我们需要确保该序列已完全执行,而不是因为过程中的中断而仅更新 Orders 表而未更新历史记录日志。因此,我们将该序列包装在事务中,在所有命令执行之前,任何更改都不会持久化到数据库。因此,有三种方法可以在会话对象中处理事务。StartTransaction
开始事务过程。然后,用户必须调用 Commit
方法将更改实际持久化到数据库,或调用 Abort
来丢弃自调用 StartTransaction
以来所做的任何更改。如果未调用 Commit
或 Abort
,然后又在同一会话上启动了另一个事务,那么新事务将被视为嵌套事务,稍后调用 Commit
将会提交两个事务。以下显示了该过程的草稿:
hr = m_session.Open(m_ds); hr = m_session.StartTransaction( ); …. Do update 1 …. Do update 2 …. Do update n m_session.Commit( );
错误处理
正如我们到目前为止所看到的,所有方法都返回 HRESULT
。我们可以使用 SUCCEEDED(hr)
宏来检查此返回值是否为成功指示器。如果我们想检查失败,则使用 FAILED(hr)
宏。
hr = m_ds.Open( ); if(SUCCEEDED(hr)) { // The connection was opened } else { // The connection failed }
Visual Studio 中的一个实用小功能是能够在 Watch 窗口中输入变量名以在调试时显示其值。如果我们输入 hr
,我们可以看到它的值。此外,如果我们输入 hr,hr
,我们将获得错误代码的常量名称。下图显示 hr
的值为 E_FAIL
而不是纯数字。
这在许多情况下对调试打开连接或执行命令的失败很有用,但如果我们还需要有关已发生错误的更详细描述呢?我将讨论三种获取错误描述的方法。
方法 1:IErrorInfo 接口
OLE DB 应用程序可以通过调用 SetErrorInfo
API 方法来指示发生了错误。消费者应用程序可以通过 GetErrorInfo
检查此错误。GetErrorInfo
返回指向 IErrorInfo
接口的指针。通过调用 IErrorInfo::GetDescription
方法,用户可以获得错误描述。以下代码片段展示了如何做到这一点:
IErrorInfo* pErrInfo; HRESULT hr = ::GetErrorInfo(0, &pErrInfo); if(SUCCEEDED(hr)) { BSTR bstrErrDescription; pErrInfo->GetDescription(&bstrErrDescription); CString strDescription = bstrErrDescription; AfxMessageBox(LPCTSTR(strDescription); pErrInfo->Release(); ::SysFreeString(bstrErrDescription); }
GetDescription
填充一个 BSTR
变量。将此类型的字符串转换为 ANSI 或 Unicode 版本有多种方法。一种方法是直接将其分配给 CString
。另一种方法是使用 OLE2T
方法。因此,我将其留给用户。完成后,我们需要做一些清理工作,通过调用接口指针上的 Release
和 SysFreeString
来释放 GetDescription
返回的 BSTR
。
方法 2:CDBErrorInfo 类
USES_CONVERSION; CDBErrorInfo einfo; BSTR bstrDescription; ULONG nRecords = 0; HRESULT hr = einfo.GetErrorRecords(&nRecords); if(SUCCEEDED(hr) && nRecords > 0) { LCID lcid = GetSystemDefaultLCID(); for(ULONG nIndex = 0; nIndex < nRecords; nIndex++) { hr = einfo.GetAllErrorInfo(nIndex, lcid, &bstrDescription); if(SUCCEEDED(hr)) { AfxMessageBox(OLE2T(bstrDescription)); SysFreeString(bstrDescription); } } }
如代码所示,调用 GetErrorRecords
来用错误数组(命名为 records
)填充 CDBErrorInfo
。我们循环遍历记录并对每条记录调用 GetAllErrorInfo
来获取描述。这是另一种做法,并且在 AtlTraceErrorRecords
函数中实现了类似的方法,该函数在方法 3 中进行了描述。
方法 3:AtlTraceErrorRecords
如果您想在查询返回时在调试器窗口中看到结果错误,您可以调用 AtlTraceErrorRecords
。它使用方法 2 中描述的算法来跟踪结果错误。您可以直接调用此方法,而无需编写代码来获取该信息。
即时创建数据库
您可能遇到过需要在用户首次使用您的应用程序登录时为用户创建数据库的情况,或者可能需要在失败时通过以编程方式重新创建数据库来恢复数据库。幸运的是,对于许多数据库类型,有一种方法可以做到这一点。像 Oracle 这样的某些数据库,至少通过 OLE DB 提供程序,不会仅仅允许您通过发出 CREATE DATABASE
方法来创建数据库,因为它需要许多步骤才能完成,但其他数据库可能允许您仅通过执行几行代码来创建数据库。我将描述一些数据库的方法,并为未能涵盖所有数据库表示歉意。
以编程方式创建 Microsoft Access 数据库
我建议两种以编程方式创建 Microsoft Access 数据库的方法:
- 这可能看起来不寻常,但它确实有效。创建一个空白的 Microsoft Access 数据库,然后根据需要复制并重命名它来创建一个新的空白数据库。以下代码片段显示了如何做到这一点。假设空白数据库副本名为 db.mdb。
BOOL CreateAccessDatabase(LPCTSTR szTemplatePath, LPCTSTR szNewDatabasePath) { return CopyFile(szTemplatePath, szNewDatabasePath, TRUE); }
szTemplatePath
是主副本数据库的路径和名称,szNewDatabasePath
是创建的数据库的新路径和名称。 - 第二种方法是使用
IDBDataSourceAdmin
接口的CreateDataSource
方法。该接口在 Microsoft.Jet.OLEDB.4.0 提供程序中实现,可用于显式创建或销毁数据源。以下函数显示了如何使用此接口在szDatabasePath
参数指定的路径创建一个空白的 .mdb 文件。此方法在知识库文章中有解释,但我在此尝试尽量避免使用接口指针,而是使用 ATL 类。BOOL CreateAccessDatabase(LPCTSTR szDatabasePath) { CDataSource ds; IDBDataSourceAdmin* pIDBDataSourceAdmin = NULL; CLSID clsid = {0xdee35070L,0x506b,0x11cf, {0xb1,0xaa,0x0,0xaa,0x0,0xb8,0xde,0x95}}; HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, __uuidof(IDBInitialize), (void**)&ds.m_spInit); if (FAILED(hr)) return FALSE; USES_CONVERSION; CDBPropSet rgPropertySet(DBPROPSET_DBINIT); rgPropertySet.AddProperty(DBPROP_INIT_DATASOURCE, T2BSTR(szDatabasePath)); hr = ds.m_spInit->QueryInterface(IID_IDBDataSourceAdmin, (void**)&pIDBDataSourceAdmin); if(FAILED(hr)) { ds.Close(); return FALSE; } hr = pIDBDataSourceAdmin->CreateDataSource(1, &rgPropertySet, NULL, IID_NULL, NULL); if(FAILED(hr)) { pIDBDataSourceAdmin->Release(); ds.Close(); return FALSE; } pIDBDataSourceAdmin->Release(); ds.Close(); return TRUE; }
以编程方式创建 MySQL 数据库
我将尝试通过在 MySQL 安装时自动设置的默认数据库“mysql”的上下文中发出“CREATE DATABASE
”命令来创建 MySQL 数据库。我将使用 CCommand
类来执行此操作,但将推迟讨论该类。以下方法在 szLocation
参数指定的目录以及 szDatabaseName
参数传递的名称处创建一个 MySQL 数据库。此外,我们还需要用户 ID 和密码才能首先连接到“mysql”数据库。
BOOL CreateMySQLDatabase(LPCTSTR szLocation, LPCTSTR szDatabaseName, LPCTSTR szUserId, LPCTSTR szPassword) { USES_CONVERSION; CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_LOCATION, T2BSTR(szLocation)); rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, T2BSTR(_T("mysql"))); rgPropertySet[0].AddProperty(DBPROP_AUTH_USERID, T2BSTR(szUserId)); rgPropertySet[0].AddProperty(DBPROP_AUTH_PASSWORD, T2BSTR(szPassword)); CDataSource ds; HRESULT hr = ds.Open(_T("MySQLProv"), rgPropertySet, 1); if(FAILED(hr)) return FALSE; CSession session; hr = session.Open(ds); if(FAILED(hr)) { ds.Close(); return FALSE; } CCommand<CNoAccessor, CNoRowset> cmd; CString strCommand; strCommand.Format(_T("CREATE DATABASE '%s';"), szDatabaseName); hr = cmd.Open(session, LPCTSTR(strCommand), NULL, NULL, DBGUID_DBSQL, false); if(FAILED(hr)) { session.Close(); ds.Close(); return FALSE; } session.Close(); ds.Close(); return TRUE; }
以编程方式创建 Microsoft SQL Server 数据库
与创建 MySQL 数据库类似,我们可以打开“master”数据库并通过它发出“CREATE DATABASE
”命令。区别在于我们将服务器位置指定为数据源,将数据库名称指定为目录。以下方法正是这样做的:
BOOL CreateSQLServerDatabase(LPCTSTR szServerName, LPCTSTR szDatabaseName, LPCTSTR szUserId, LPCTSTR szPassword) { USES_CONVERSION; CDBPropSet rgPropertySet[1] = {DBPROPSET_DBINIT}; rgPropertySet[0].AddProperty(DBPROP_INIT_CATALOG, T2BSTR(_T("master"))); rgPropertySet[0].AddProperty(DBPROP_INIT_DATASOURCE, T2BSTR(szServerName)); rgPropertySet[0].AddProperty(DBPROP_AUTH_USERID, T2BSTR(szUserId)); rgPropertySet[0].AddProperty(DBPROP_AUTH_PASSWORD, T2BSTR(szPassword)); CDataSource ds; HRESULT hr = ds.Open(_T("SQLOLEDB"), rgPropertySet, 1); if(FAILED(hr)) return FALSE; CSession session; hr = session.Open(ds); if(FAILED(hr)) { ds.Close(); return FALSE; } CCommand<CNoAccessor, CNoRowset> cmd; CString strCommand; strCommand.Format(_T("CREATE DATABASE '%s';"), szDatabaseName); hr = cmd.Open(session, LPCTSTR(strCommand), NULL, NULL, DBGUID_DBSQL, false); if(FAILED(hr)) { session.Close(); ds.Close(); return FALSE; } session.Close(); ds.Close(); return TRUE; }
我们省略了一些问题,例如对于 MySQL 或 Microsoft SQL Server 数据库需要 SSPI 身份验证的集成安全性属性。只需添加一个布尔属性 DBPROP_INIT_INTEGRATED
即可打开 mysql 或 master 数据库。
摘要
现在我们可以打开到许多主要数据库类型的连接并启动会话来完成工作。我们还看到了以编程方式创建数据库的示例。