前面接手一个项目,需要用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;
CString connectsource;
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连接非本机数据库的时间。这仅是一个设想,在下一步的系统升级时希望能实现,也恳请计算机高手们提出意见和建议。