|          
摘 要:本文在介绍ODBC技术的基础上,将MFC和ODBC API结合起来创建了两个自定义 类,实现了对任意异构型数据库库结构信息的获取。
 关键字:ODBC,MFC,异构型数据库,记录集
 
 一. 问题的提出
 随着数据库技术在各个应用领域的迅速发展,市场上推出了多种数据库系
 统,为了充分利用资源,实现信息共享,以便用户能对异构型数据库实现透明的
 访问(包括数据查询、更新和交换等功能),作者开发了异构型数据库通信平台。
 在平台的研制过程中,获取各种异构型数据库的结构信息是进行数据访问的前
 提。作者以VC++5.0为开发语言,利用ODBC实现了这一关键技术。
 
 二. ODBC技术介绍
 ODBC技术是指开放性数据库连接技术,该技术使应用程序无需关心数据源来
 自何种DBMS,利用其标准接口实现与数据源之间的数据交换。传统的ODBC编程是
 利用高级语言(如C语言)调用ODBC的API来实现。应用程序要求驱动程序管理
 器和每个驱动程序为ODBC环境、每个连接以及每个SQL语句分配信息存储空间,
 并返回指向各个存储区的句柄供其调用。ODBC接口定义了三种句柄类型:
 环境句柄:为全程信息标识内存存储,包括有效连接句柄及当前活动连接句柄。
 ODBC将环境句柄定义为HENV类型的变量。应用程序使用单一环境句柄,它必须
 在连接到数据源前请求该句柄。
 连接句柄:为特定连接的信息标识了内存存储。ODBC将连接句柄定义为
 HDBC类型。应用程序必须在连接到数据源前请求连接句柄。每个连接句柄与环
 境句柄有关。然而,环境句柄可以有多个与其有关的连接句柄。
 语句句柄:为SQL语句信息标识内存存储。ODBC将语句句柄定义为HSTMT类型变
 量。应用程序必须在提交SQL请求之前请求语句句柄。每个语句句柄与一个连接
 句柄有关。然而,每个连接句柄可以有多个与其相关的语句句柄。
 下面以C语言为例说明传统ODBC编程的一般过程。
 1、 环境申请,分配环境句柄
 HENV henv;
 SQLAllocEnv(&henv);
 说明:分配一个环境句柄,支持一个或多个数据源连接。
 2、 连接申请,分配连接句柄
 HDBC hdbc;
 SQLAllocConnect(henv,&hdbc);
 说明:一个连接句柄对应一个数据源,可以有多个连接句柄。
 3、 连接数据源,用连接句柄连接到数据源
 SQLDriverConnect(hdbc,...);
 说明:以对话框方式获取注册信息,并连接数据源。
 4、 语句申请,分配语句句柄
 SQLAllocStmt(hdbc,&hstmt);
 说明:获得语句句柄,以便执行SQL语句。
 5、 执行SQL语句
 SQLExecDirect(hstmt,SQLStatement,..);
 说明:利用语句句柄,执行SQL语句。
 6、 释放所有资源
 SQLfreeStemt(hstmst,...); //释放语句句柄
 SQLDisconnect(hdbc); //断开连接
 SQLFreeConnect(hdbc); //释放当前数据库连接句柄
 SQLFreeEnv(henv); //释放环境句柄
 
 三. 利用VC++和ODBC技术获取异构型数据库结构信息
 传统的ODBC编程过程比较复杂,各种参数不易理解,且直接获取返回的数据较
 困难。VC++ 5.0的MFC类库对ODBC的API进行封装,部分简化了ODBC编程(尤其
 是对数据库记录集的操作),但单纯利用MFC类获取异构型数据库的结构信息仍
 然比较困难,因此需要将MFC和传统ODBC API编程结合起来。作者利用ODBC接口
 函数重载了MFC中CRecordset类的部分成员函数,创建CTable和CColumns类。利
 用这两个新创建的类,可以很方便的获取异构型数据库结构信息。
 下面就是关于CTable和Ccolumns类的定义:
 class CTable : public CRecordset
 {
 virtual CString GetDefaultConnect() { return ""; }
 virtual CString GetDefaultSQL() { return ""; }
 public:
 CTable(CDatabase* pDatabase);
 BOOL             Open(LPCSTR pszTableQualifier = NULL,
 LPCSTR pszTableOwner = NULL,
 LPCSTR pszTableName = NULL,
 LPCSTR pszTableType = NULL,
 UINT nOpenType = forwardOnly);
 CString             m_strTableQualifier;
 CString             m_strTableOwner;
 CString             m_strTableName;
 CString             m_strTableType;
 CString             m_strRemarks;
 virtual void    DoFieldExchange(CFieldExchange*);
 };
 
 class CColumns : public CRecordset
 {
 virtual CString GetDefaultConnect() { return ""; }
 virtual CString GetDefaultSQL() { return ""; }
 public:
 CColumns(CDatabase* pDatabase);
 BOOL             Open(LPCSTR pszTableQualifier = NULL,
 LPCSTR pszTableOwner = NULL,
 LPCSTR pszTableName = NULL,
 LPCSTR pszColumnName = NULL,
 UINT nOpenType = forwardOnly);
 CString             m_strTableQualifier;
 CString             m_strTableOwner;
 CString             m_strTableName;
 CString             m_strColumnName;
 int                 m_nDataType;
 CString             m_strTypeName;
 long             m_nPrecision;
 long             m_nLength;
 int                 m_nScale;
 int                 m_nRadix;
 int                 m_fNullable;
 CString             m_strRemarks;
 virtual void    DoFieldExchange(CFieldExchange*);
 };
 BOOL CColumns::Open(LPCSTR pszTableQualifier,
 LPCSTR pszTableOwner,LPCSTR pszTableName,LPCSTR pszColumnName,
 UINT nOpenType)
 {
 RETCODE    nRetCode;
 UWORD    bFunctionExists;
 //检验是否支持SQLColumns函数
 AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
 SQL_API_SQLCOLUMNS,&bFunctionExists));
 if (!Check(nRetCode) || !bFunctionExists)
 {
 if (!bFunctionExists)
 TRACE(_T("SQLColumns 不支持\n"));
 return FALSE;
 }
 //设置缓冲区状态,分配语句句柄
 SetState(nOpenType,NULL,readOnly);
 if (!AllocHstmt())
 return FALSE;
 TRY
 {
 OnSetOptions(m_hstmt);
 AllocStatusArrays();
 // 调用ODBC的SQLColumns函数
 AFX_ODBC_CALL(::SQLColumns(m_hstmt,
 (UCHAR FAR*)pszTableQualifier,SQL_NTS,
 (UCHAR FAR*)pszTableOwner,SQL_NTS,
 (UCHAR FAR*)pszTableName,SQL_NTS,
 (UCHAR FAR*)pszColumnName,SQL_NTS));
 if (!Check(nRetCode))
 ThrowDBException(nRetCode,m_hstmt);
 // 分配内存,填写信息
 AllocAndCacheFieldInfo();
 AllocRowset();
 MoveNext();
 m_bBOF = m_bEOF;
 }
 
 //异常信息的捕获
 CATCH_ALL(e)
 {
 Close();
 THROW_LAST();
 }
 END_CATCH_ALL
 return TRUE;
 }
 //获取记录集信息
 void CColumns::DoFieldExchange(CFieldExchange* pFX)
 {
 pFX->SetFieldType(CFieldExchange::outputColumn);
 RFX_Text(pFX,_T("TABLE_QUALIFIER"),m_strTableQualifier);
 RFX_Text(pFX,_T("TABLE_OWNER"),m_strTableOwner);
 RFX_Text(pFX,_T("TABLE_NAME"),m_strTableName);
 RFX_Text(pFX,_T("COLUMN_NAME"),m_strColumnName);
 RFX_Int(pFX,_T("DATA_TYPE"),m_nDataType);
 RFX_Text(pFX,_T("TYPE_NAME"),m_strTypeName);
 RFX_Long(pFX,_T("PRECISION"),m_nPrecision);
 RFX_Long(pFX,_T("LENGTH"),m_nLength);
 RFX_Int(pFX,_T("SCALE"),m_nScale);
 RFX_Int(pFX,_T("RADIX"),m_nRadix);
 RFX_Int(pFX,_T("NULLABLE"),m_fNullable);
 RFX_Text(pFX,_T("REMARKS"),m_strRemarks);
 }
 CColumns::CColumns(CDatabase* pDatabase): CRecordset(pDatabase)
 {
 m_strTableQualifier    = _T("");
 m_strTableOwner        = _T("");
 m_strTableName        = _T("");
 m_strColumnName        = _T("");
 m_nDataType             = 0;
 m_strTypeName        = _T("");
 m_nPrecision        = 0;
 m_nLength             = 0;
 m_nScale             = 0;
 m_nRadix             = 0;
 m_fNullable             = 0;
 m_strRemarks        = _T("");
 m_nFields = 12;
 }
 
 CTable::CTable(CDatabase* pDatabase): CRecordset(pDatabase)
 {
 m_strTableQualifier    = _T("");
 m_strTableOwner        = _T("");
 m_strTableName        = _T("");
 m_strTableType        = _T("");
 m_strRemarks        = _T("");
 m_nFields = 5;
 }
 
 BOOL CTable::Open(LPCSTR pszTableQualifier,
 LPCSTR pszTableOwner,LPCSTR pszTableName,LPCSTR pszTableType,
 UINT nOpenType)
 {
 RETCODE    nRetCode;
 UWORD    bFunctionExists;
 //检验是否支持SQLTables 函数
 AFX_SQL_SYNC(::SQLGetFunctions(m_pDatabase->m_hdbc,
 SQL_API_SQLTABLES,&bFunctionExists));
 if (!Check(nRetCode) || !bFunctionExists)
 {
 if (!bFunctionExists)
 TRACE(_T("SQLTables 不支持\n"));
 return FALSE;
 }
 //设置缓冲区状态,分配语句句柄
 SetState(nOpenType,NULL,readOnly);
 if (!AllocHstmt())
 return FALSE;
 TRY
 {
 OnSetOptions(m_hstmt);
 AllocStatusArrays();
 //调用 ODBC的SQLTables函数
 AFX_ODBC_CALL(::SQLTables(m_hstmt,
 (UCHAR FAR*)pszTableQualifier,SQL_NTS,
 (UCHAR FAR*)pszTableOwner,SQL_NTS,
 (UCHAR FAR*)pszTableName,SQL_NTS,
 (UCHAR FAR*)pszTableType,SQL_NTS));
 if (!Check(nRetCode))
 ThrowDBException(nRetCode,m_hstmt);
 // 分配内存,填写信息
 AllocAndCacheFieldInfo();
 AllocRowset();
 MoveNext();
 m_bBOF = m_bEOF;
 }
 //异常信息的捕获
 CATCH_ALL(e)
 {
 Close();
 THROW_LAST();
 }
 END_CATCH_ALL
 return TRUE;
 }
 void CTable::DoFieldExchange(CFieldExchange* pFX)
 {
 pFX->SetFieldType(CFieldExchange::outputColumn);
 RFX_Text(pFX,_T("TABLE_QUALIFIER"),m_strTableQualifier);
 RFX_Text(pFX,_T("TABLE_OWNER"),m_strTableOwner);
 RFX_Text(pFX,_T("TABLE_NAME"),m_strTableName);
 RFX_Text(pFX,_T("TABLE_TYPE"),m_strTableType);
 RFX_Text(pFX,_T("REMARKS"),m_strRemarks);
 }
 以上两个类对CRecordset的Open和DoFieldExchange函数进行了重载。应用程序
 可以在需要时创建CTable或Ccolumns类,并调用OPEN成员函数建立相应的表结构
 和字段结构记录集。接下来就可以通过下列函数来遍历异构型数据库的结构信息
 了。
 Void CRecordset::MoveFirst(); //移到第一条记录
 Void CRecordset::MoveLast(); //移到最后一条记录
 Void CRecordset::MovePrev(); //移到前一条记录
 Void CRecordset::MoveNext(); //移到后一条记录
 BOOL CRecordset::IsBOF(); //判断是否到达第一条记录前
 BOOL CRecordset::IsEOF(); //判断是否到达最后一条记录后
 
 四、结束语
 利用自定义的CTable和Ccolumns类,应用程序能获取任何异构型数据库库结构
 信息。根据获得的信息可以方便的对未知数据库进行相应的操作。若将CTable和
 Ccolumns类与文档类、视类结合起来,就可以在窗口里以一定的方式显示结构
 信息。作者利用以上技术在异构型数据库通信平台上成功实现了对各种异构型
 数据库库结构信息的获取。
 
 |