如何使用Ado访问PARADOX数据库?
前面接手一个项目,需要用VC访问已经存在的PARADOX数据库。在接手这个项目前,对于PARADOX的理解少之又少,只知道有这么一种数据库,并不了解它的结构是什么。真正对它进行操作的时候,才发现是如此之难。有几次差点都放弃了,但最后一咬牙,总算坚持过来了。在这期间,我走了不少的弯路,也有了一些心得,下面写下来,做为一个总结,也为其他同道少走一点弯路起一点提示作用吧。
1、PARADOX数据库结构 PARADOX数据库是Boland以前在DELPHI下利用BDE进行操作的桌面数据库,目前已经很少使用,以致ADO都不提供它的引擎了(也害得我吃了不少苦头)。PARADOX数据库本身以独立的表存在的,一个表就可以看成是一个库,或者也可以说是一个文件夹就是一个库,文件夹里的PARADOX数据表就是该库的各个表。PARADOX数据表的扩展名是db,此外还有一些其他的文件类型,作为数据表的辅助,但用ADO对其进行处理时,使用*.DB的文件就已经足够。 2、连接到PARADOX数据库 前面已经提到过,ADO没有PARADOX数据库的引擎,要用ADO访问PARADOX数据库,我试过三种方式来进行操作:用Microsoft.Jet.OLEDB.4.0来替代PARADOX数据库引擎;用仿ODBC的连接语句操作;建立ODBC数据源,然后用ADO来访问ODBC。还有一种是用VC来封装BDE API,在ww.codeproject.com上可以找到相关的内容。对于最后一种方法,用他的例子却实效果不错,但真正移直到我的程序上时却费了很大劲,并且效果不好,主要是我对于BDE操作方式太不理解了,花了好多时间,最终以放弃告终。 下面我就说明用前三种方式来进行连接到数据库的操作。其实这三种方式都没有太大的差别,只是连接语句不同而已。 (1)用Microsoft.Jet.OLEDB.4.0引擎。这种方式访问时跟连接到其他数据库没什么差别: _ConnectionPtr m_pDb; CStringconnectsource; connectsource.Format(L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%s//shared;Extended Properties=Paradox 5.x;Persist Security Info=False",strCTRSRoute); try//检查数据库连接是否正常 { m_pDb.CreateInstance(__uuidof(Connection)); m_pDb->ConnectionTimeout=10; m_pDb->CommandTimeout=20; if(m_pDb->State!=adStateClosed) { m_pDb->Close(); m_pDb->Open((_bstr_t)connectsource,"","",adModeUnknown); } else { hr=m_pDb->Open((_bstr_t)connectsource,"","",adModeUnknown); } } catch(_com_error e) //捕捉异常 { LogAdoErrorImport(m_pDb); } 说明:connectsource变量保存了连接信息,由此我们可以看到,所谓数据库的数据源,仅指是连接到PARADOX数据表放的位置Data Source=%s//shared,“shared”为一文件夹名,在该文件夹下面有PARADOX数据表,而不是具体指向哪一个数据表。同理,在下面的两种方式中,数据源也仅指向包含PARADOX数据表的文件夹。除了异常处理外在后面要详细说明外,其他的操作与其他ADO操作没有差别,在此不再冗续。 (2)用仿ODBC连接操作。ODBC还提供了PARADOX数据引擎,因此,我们可以通过ODBC来访问PARADOX数据表。本方法就是把建立DSN的连接信息在ADO的连接语句中完全地写出来。 connectsource.Format(L"CollatingSequence=ASCII;DBQ=%s//shared;" / L"DefaultDir=%s//shared;Driver={Microsoft Paradox Driver (*.db )};" / L"DriverId=538;FIL=Paradox 5.X;" / L"MaxBufferSize=2048;MaxScanRows=8;PageTimeout=600;" / L"ParadoxNetPath=%s//shared;ParadoxNetStyle=4.x;ParadoxUserName=admin;" / L"SafeTransactions=0;Threads=3;UID=admin;UserCommitSync=Yes;",strCTRSRoute,strCTRSRoute,strCTRSRoute); 说明:上面所有信息均可在注册表中的ODBC对应的键下面或都文件DSN中直接找到。我们现在操作的PARADOX数据表,一般是Paradox 4.X或Paradox 5.X。对于Paradox 7.X好像也无能为力。好在我用的数据库的版本也没那么高,呵呵。其他操作同第一种方式。 (3)建立ODBC数据源,用ADO访问ODBC数据源。这种方式是第二种的翻版,但需要动态地或手工添加ODBC数据源。 CString connectsource="Provider=MSDASQL.1;Persist Security Info=False;Data Source=Projdir"; 其中Data Source=Projdir,就指出了数据源为一个ODBC的DSN。 为了实现动态地添加数据源,下面提供一个添加PARADOX 系统DSN的函数: BOOL LoadDbSource(CString strSourceName, CString strSourceDb, CString strDescription) { //存放打开的注册表键 HKEY hKey; DWORD dw; //存放注册表API函数执行的返回值 LONG lReturn; //存放要打开的子键 CString strSubKey; //检测是否安装了MS Access ODBC driver:odbcjt32.dll //获得 Windows系统目录 WCHAR sysDir[MAX_PATH]; WCHAR drvName[]=L"//odbcjt32.dll"; ::GetSystemDirectory(sysDir, MAX_PATH); wcscat(sysDir,drvName); CFileFind findFile; if(!findFile.FindFile(sysDir)) { AfxMessageBox(L"没有安装Paradox 5.X的ODBC驱动程序odbcjt32.dll,/n无法加载该类数据源!" ,MB_OK | MB_ICONSTOP); return false; } strSubKey=L"SOFTWARE//ODBC//ODBC.INI//" + strSourceName; //创建 ODBC数据源在注册表中的子键 lReturn=::RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)strSubKey, 0, NULL, REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,&hKey,&dw); if(lReturn != ERROR_SUCCESS) return FALSE; //设置数据源的各项参数 CString strDbq = strSourceDb; CString strDriver = sysDir; DWORD dwDriverId = 538; CString strFil = "Paradox 5.X;"; //CString strPwd = strSourceName; DWORD dwSafeTransactions = 0; CString strUid =L"admin"; ::RegSetValueEx(hKey, L"DefaultDir", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strDbq), 2*strDbq.GetLength()); ::RegSetValueEx(hKey, L"Description", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strDescription), 2*strDescription.GetLength()); ::RegSetValueEx(hKey, L"Driver", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strDriver), 2*strDriver.GetLength()); ::RegSetValueEx(hKey, L"DriverId", 0L, REG_DWORD, (CONST BYTE*)(&dwDriverId), sizeof(dw)); ::RegSetValueEx(hKey, L"FIL", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strFil),2*strFil.GetLength ()); ::RegSetValueEx(hKey, L"UID", 0L, REG_SZ, (CONST BYTE*)((LPCTSTR)strUid),2*strUid.GetLength ()); ::RegSetValueEx(hKey, L"SafeTransactions", 0L, REG_DWORD, (CONST BYTE*)(&dwSafeTransactions), sizeof(dw)); ::RegCloseKey(hKey); //创建 ODBC数据源的Jet子键 strSubKey += "//Engines//Paradox"; lReturn=::RegCreateKeyEx(HKEY_LOCAL_MACHINE, (LPCWSTR)strSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dw); if(lReturn != ERROR_SUCCESS) return FALSE; //设置该子键下的各项参数 CString strImplict=""; CString strUserCommit="Yes"; DWORD dwPageTimeout=5; DWORD dwThreads=3; DWORD dwMaxBufferSize=2048; CString strCollSeq=L"ASCII"; CString strParadoxNetStyle=L"4.x"; ::RegSetValueEx(hKey, L"ImplicitCommitSync", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strImplict), 2*strImplict.GetLength()+1); //::RegSetValueEx(hKey, L"MaxBufferSize", 0L, REG_DWORD, (CONST BYTE*)(&dwMaxBufferSize), sizeof(dw)); ::RegSetValueEx(hKey, L"PageTimeout", 0L, REG_DWORD, (CONST BYTE*)(&dwPageTimeout), sizeof(dw)); ::RegSetValueEx(hKey, L"Threads", 0L, REG_DWORD, (CONST BYTE*)(&dwThreads), sizeof(dw)); ::RegSetValueEx(hKey, L"UserCommitSync", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strUserCommit), 2*strUserCommit.GetLength()); ::RegSetValueEx(hKey, L"CollatingSequence", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strCollSeq), 2*strCollSeq.GetLength()); ::RegSetValueEx(hKey, L"ParadoxNetPath", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strDbq), 2*strDbq.GetLength()); ::RegSetValueEx(hKey, L"ParadoxNetStyle", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strParadoxNetStyle), 2*strParadoxNetStyle.GetLength()); ::RegSetValueEx(hKey, L"ParadoxUserName", 0L, REG_SZ, (CONST BYTE*)((LPCWSTR)strUid), 2*strUid.GetLength()); ::RegCloseKey(hKey); //设置ODBC数据库引擎名称 lReturn=::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE//ODBC//ODBC.INI//ODBC Data Sources", 0L, KEY_WRITE, &hKey); if(lReturn != ERROR_SUCCESS) return false; CString strDbType=L"Microsoft Paradox Driver (*.db )"; ::RegSetValueEx(hKey, strSourceName, 0L, REG_SZ, (CONST BYTE*)((LPCTSTR)strDbType), 2*strDbType.GetLength()); return true; } 说明:该函数是在网上一个写ODBC注册表的例子上加工而成的,在此表示感谢!在建立系统DSN的时候,一个项是ParadoxUserName。在利用ODBC管理器添加数据源的时候,会默认为当前用户的登录名。并且此项是必须的。为了减少去获得当前系统用户的麻烦,将它的值赋为:“amdin”,在实际的运行过程中,没有产生任何负面影响。 以上是连接到数据库的操作。所有上述的操作没有多大的差别。 3、对数据表操作, 本来当连接到数据库后,对于数据表的操作就是一件很容易的事了。但我却在这个环节上花费了大量的时间和精力,以致开发时间一加再加。对于表的操作我不想说太多,但这里面一个问题却不得不说。 我没有真正地却研究PARADOX底层原理是什么,但在实际操作时,却发现它对BDE有很强的依赖性。由于我的计算机上曾经装过用BDE开发的数据库产品,因此,所有操作一切正常。但当该系统拿到其他计算机上就出现了很多问题,其中最主要的就是出现“[ODBC PARADOX]外部数据表不是预期格式”的错误。后来经过多台计算机上总结,比较得出可能是BDE引起的。后来在有问题的计算机上安装BDE后,一切问题都没有了。 4、错误处理 本来,对ADO进行操作时,进行错误处理是必要的步骤,但如何进行错误处理的方式也能影响程序的健壮与稳定。一般情况下,我们都是利用_com_error对象提供错误信息,在这个过程中,我发觉并不能得到错误的真正信息,而是一些模棱两可的信息如:3092,Dispatch error等等,对于我们解决问题没有多少效果。错误的信息获得最好方法就是利用ADO本身的ERROR对象。它能捕捉所有CONNECTION,COMMAND和RECODSET的信息,下面这个函数也是从www.codeproject.com上得到的。在此对该函数作者表示感谢! HRESULT LogAdoErrorImport(_ConnectionPtr pConn) { ErrorsPtr pErrors; ErrorPtr pError; CString strTmp; HRESULT hr = (HRESULT) 0L; long nCount; // Don't have an un-handled exception in the handler that // handles exceptions! try { pErrors = pConn->GetErrors(); nCount = pErrors->GetCount(); for( long i = 0; (!FAILED(hr)) && (i < nCount); i++ ) { TRACE( L"/t Dumping ADO Error %d of %d", i+1, nCount ); hr = pErrors->get_Item((_variant_t)((long)i), &pError ); _bstr_t bstrSource ( pError->GetSource() ); _bstr_t bstrDescription( pError->GetDescription() ); _bstr_t bstrHelpFile ( pError->GetHelpFile() ); _bstr_t bstrSQLState ( pError->GetSQLState() ); TRACE( L"/n Number = %ld", pError->GetNumber() ); TRACE( L"/n Source = %s", (LPCTSTR) bstrSource ); CString strDes; strDes.Format(L"%s/n", (LPCTSTR) bstrDescription ); AfxMessageBox(strDes,MB_OK | MB_ICONERROR); TRACE( L"/n HelpFile = %s", (LPCTSTR) bstrHelpFile ); TRACE( L"/n HelpContext = %ld", pError->GetHelpContext() ); TRACE( L"/n SQLState = %s", (LPCTSTR) bstrSQLState ); TRACE( L"/n HelpContext = %ld", pError->GetHelpContext() ); TRACE( L"/n NativeError = %ld", pError->GetNativeError() ); } } catch( CException *e ) { TRACE( L"*** UNABLE TO LOG EXCEPTION ***" ); e->Delete(); } catch(...) { TRACE( L"*** UNABLE TO LOG EXCEPTION ***" ); } pErrors->Release(); pError->Release(); return hr; } 要调用此函数,也只需在CATCH中调用即可,函数参数为_ConnectionPtr型。 catch(_com_error e) //捕捉异常 { LogAdoErrorImport(m_pDb); } 5、其他 在我所接手的这个项目中,要对局域网内服务器上不同文件夹下的多个PARADOX数据表进行操作,也就是对多个数据库进行操作。现在的处理方式就是共享这些文件夹,并在本机建立网络映射。这样的操作,如果网络比较好的话还可以接受,当网络不太好的时候,操作起来就很困难,原因是对频繁对数据库进行连接操作占用了大量的时间。因此,我想到利用C/S模式,在服务器上安装处理这些数据库操作的服务,客户端把所有的请求发到服务器端,当服务器端处理好后直接传回数据。这样就减少了通过ADO连接非本机数据库的时间。这仅是一个设想,在下一步的系统升级时希望能实现,也恳请计算机高手们提出意见和建议。