snoopy每日一译-完美的ADO[2](下)
by:Bob Place 1999.6.27
from:codeGuru
翻译:snoopy
可以建立两个同名方法,一个返回符号分割字符串(方法名有前缀”s”),另一个返回记录集:
STDMETHODIMP CADO::GetCustomerNameAndBalance(BSTR CustKey, _Recordset **Result)
{
// std stream used to create the SQL statement
std::strstream SQL;
// Put the BSTR into something we can deal with
_bstr_t Holder(CustKey);
SQL.str("");
SQL<<"SELECT a.cust_name, b.cust_balance FROM cust_info as a, cust_credit as b WHERE a.cust_id = '<<(char*)Holder<<" AND a.cust_key = b.cust_key";
ADOExecute(_bstr_t(SQL.str().c_str()),_bstr_t(","),Result);
return S_OK;
}
唯一不同的是我们使用ADOExecute方法,而不使用ADOExecuteReturnDelimited方法。客户端使用:
(VB)dim MySet as object
MySet = MyADOObject.GetCustomerNameAndBalance(CustID)
(VC)
_RecorsetPtr MySet;
MyADOObject->GetCustomerNameAndBalance(CustID,&MySet);
好,现在我们可以从客户端运行SQL语句,但如何运行存储程序呢?为了实现它,我们建立2个方法。一个运行存储程序并返回分割字符串,另外一个返回记录集。让我们首先看看返回记录集的方法:
STDMETHODIMP CADO::ADOExecuteSP(BSTR SPName, VARIANT ParameterString, _Recordset **Result)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
if(m_Recordset == NULL)
m_Recordset.CreateInstance(__uuidof(Recordset));
if(m_Recordset->State == adStateOpen)
m_Recordset->Close();
try
{
// Now set up the command object
m_Command->ActiveConnection = m_Connection;
// enter the SP name
m_Command->CommandText = _bstr_t(SPName);
// Now execute it
m_Recordset = m_Command->Execute(&RecordsEffected,&ParameterString,adCmdStoredProc);
*Result = m_Recordset.Detach();
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
首先,我们检查确认全局的recordset有效,但不打开:
if(m_Recordset == NULL)
m_Recordset.CreateInstance(__uuidof(Recordset));
if(m_Recordset->State == adStateOpen)
m_Recordset->Close();
接着,我们赋值给m_Command->ActiveConnection,并将存储程序名赋予m_Command->CommandText:
// Now set up the command object
m_Command->ActiveConnection = m_Connection;
// enter the SP name
m_Command->CommandText = _bstr_t(SPName);
最后,我们必须调用Command.Execute方法,并获得记录集:
m_Recordset = m_Command->Execute(&RecordsEffected,&ParameterString,adCmdStoredProc);
*Result = m_Recordset.Detach();
让我们看看参数列表。我们需要传送的参数是VARIANT数组(是否想到了SAFEARRAY?),在VB中,我们很容易实现:
(VB)
dim MySet as Object
MySet = MyADOObject.ADOExecuteSP("some_stored_procedure",Array("param_1",2,"param_3))
(VC)
SAFEARRAYBOUND MyBound[1];
MyBound[0].cElements = 2;
MyBound[0].lLbound = 0;
VARIANT ParamHolder;
VariantInit(ParamHolder);
ParamHolder->vt = VT_VARIANT | VT_ARRAY;
pVariant->parray = SafeArrayCreate(VT_VARIANT,2,MyBound);
VARIANT Param1(SysAllocString("123ABC");
VARIANT Param2(100);
int MyPosition;
MyPosition = 0;
SafeArrayPutElement(pVariant->parray,MyPosition,&Param1);
SafeArrayPutElement(pVariant->parray,lDimension,&Param2);
_RecordsetPtr MySet;
MyADOObject->ADOExecuteSP(_bstr_t("some_stored_procedure"),ParamHolder,&MySet);
VC中,我们需要做多一点。
当然,我们还需要一个返回符号分割字符串的姐妹方法:
STDMETHODIMP CADO::ADOExecuteSPReturnDelimited(BSTR SPName, VARIANT ParameterArray, BSTR Delimiter, VARIANT *Result)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
*Result = _variant_t("");
try
{
_RecordsetPtr PopSet;
PopSet.CreateInstance(__uuidof(Recordset));
PopSet->CursorLocation = adUseClient;
if(m_Connection->State != adStateOpen)
{
*Result = _variant_t("VISP_COM_ERROR_CONNECTION_NOT_OPEN");
return S_FALSE;
}
// Now set up the command object
m_Command->ActiveConnection = m_Connection;
// enter the SP name
m_Command->CommandText = _bstr_t(SPName);
// Now execute it
PopSet = m_Command->Execute(&RecordsEffected,&ParameterArray,adCmdStoredProc);
if(!PopSet->adoEOF)
{
BSTR bstrResult = NULL;
_bstr_t btColDelim(Delimiter);
_bstr_t btRowDelim(L"\r\n");
_bstr_t btNullExp(L"");
PopSet->raw_GetString(adClipString,-1,btColDelim,btRowDelim,btNullExp,&bstrResult);
*Result = _variant_t(bstrResult);
}
else
*Result = _variant_t("");
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
客户端调用:
(VB)
MyResult = MyADOObject.ADOExecuteSPReturnDelimited("some_stored_procedure",Array("param_1",2,"param_3), ",")
(VC)
SAFEARRAYBOUND MyBound[1];
MyBound[0].cElements = 2);
MyBound[0].lLbound = 0;
VARIANT ParamHolder;
VariantInit(ParamHolder);
ParamHolder->vt = VT_VARIANT | VT_ARRAY;
pVariant->parray = SafeArrayCreate(VT_VARIANT,2,MyBound);
VARIANT Param1(SysAllocString("123ABC");
VARIANT Param2(100);
int MyPosition;
MyPosition = 0;
SafeArrayPutElement(pVariant->parray,MyPosition,&Param1);
SafeArrayPutElement(pVariant->parray,lDimension,&Param2);
VARIANT MyResult;;
MyADOObject->ADOExecuteSPReturnDelimited(_bstr_t("some_stored_procedure"),ParamHolder,_bstr_t(","),&MyResult);
现在,让我们来做最后一步:加入一个基于存储程序的返回记录集和符号分割字符串的专用方法。客户端只需要知道这个方法需要一个Rep key作为参数。在我们的数据库中,我们建立了一个存储程序GetEmployeeInfo,它需要一个参数,就是employee ID。
STDMETHODIMP CADO: GetEmployeeInfo (BSTR RepKey, _Recordset **Result)
{
SAFEARRAYBOUND MyBound[0];
MyBound[0].cElements = 1);
VARIANT ParamHolder;
VariantInit(ParamHolder);
ParamHolder->vt = VT_VARIANT | VT_ARRAY;
pVariant->parray = SafeArrayCreate(VT_VARIANT,1,MyBound);
VARIANT Param1(RepKey);
int MyPosition;
MyPosition = 0;
SafeArrayPutElement(pVariant->parray,MyPosition,&Param1);
ADOExecuteSP(_bstr_t("GetEmployeeInfo"),ParamHolder,Result);
}
客户端调用:
(VB)
dim MySet as Object
MySet = MyADOObject.GetEmployeeInfo("123ABC")
(VC)
_RecordsetPtr MySet;
MyADOObject->GetEmployeeInfo(_bstr_t("123ABC"),&MySet);
客户端的调用很简单。客户端无须知道存储程序的名字,存储程序的参数,怎样使用Command对象。对于VC客户端来说,它无须知道如何使用SAFEARRAY。
现在看看返回符号分割字符串的方法:
STDMETHODIMP CADO:: sGetEmployeeInfo (BSTR RepKey, VARIANT *Result)
{
SAFEARRAYBOUND MyBound[0];
MyBound[0].cElements = 1);
VARIANT ParamHolder;
VariantInit(ParamHolder);
ParamHolder->vt = VT_VARIANT | VT_ARRAY;
pVariant->parray = SafeArrayCreate(VT_VARIANT,1,MyBound);
VARIANT Param1(RepKey);
int MyPosition;
MyPosition = 0;
SafeArrayPutElement(pVariant->parray,MyPosition,&Param1);
ADOExecuteSPReturnDelimited(_bstr_t("GetEmployeeInfo"),ParamHolder,_bstr_t(","),Result);
}
客户端使用:
(VB)
MyResult = MyADOObject.sGetEmployeeInfo("123ABC")
(VC)
VARIANT MyResult;
MyADOObject->sGetEmployeeInfo(_bstr_t("123ABC"),&MyResult);
好,现在我们知道了:
1、 建立一个ATL COM对象很容易。只要跟着向导做就可以了。
2、 可以使用向导往接口中添加方法和属性。
3、 在ATL对象中添加ADO支持和在应用程序中添加一样简单。
4、 如何去传送VARIANT, BSTR, 和 SAFEARRAY类型的参数。
5、 VB和VC客户端调用方法的不同。
6、 怎样去使用SAFEARRAY, _bstr_t 和_variant_t。
7、 怎样包封基于查询的ADO方法去返回一个记录集或一个符号分割字符串。
8、 通过Command对象的接口,执行存储程序,并返回一个记录集或一个符号分割字符串。
9、 怎样隐藏数据库的信息,使客户端无须知道数据库怎样组织。
现在,我向你介绍一些很酷的技巧!
当我们开发ASP网页的时候我们会经常使用ADO COM对象。在网页中,我们也经常使用表格。现在我告诉你如何去基于查询或存储程序得到一个表格:
STDMETHODIMP CADO::ADOMakeWebTable(BSTR SQL, VARIANT *Result)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
BSTR bstrResult = NULL;
std::strstream Holder;
try
{
_RecordsetPtr PopSet;
PopSet.CreateInstance(__uuidof(Recordset));
PopSet->CursorLocation = adUseClient;
if(m_Connection->State != adStateOpen)
{
*Result = _variant_t("VISP_COM_ERROR_CONNECTION_NOT_OPEN");
return S_FALSE;
}
PopSet = m_Connection->Execute((char*)_bstr_t(SQL),&RecordsEffected,adCmdText);
if(!PopSet->adoEOF)
{
_bstr_t btColDelim(L"</TD><TD>");
_bstr_t btRowDelim(L"</TD</TR><TR><TD>");
_bstr_t btNullExp(L"");
PopSet->raw_GetString(adClipString,-1,btColDelim,btRowDelim,btNullExp,&bstrResult);
Holder.str("");
Holder<<"<TABLE BORDER="<<m_Border<<" BGCOLOR="<<(char*)m_BGColor<<" STYLE='color="<<(char*)m_FGColor<<"'><TR><TD>"<<(char*)_bstr_t(bstrResult)<<"</TD></TR></TABLE>";
*Result = _variant_t(_bstr_t(Holder.str().c_str()));
}
else
*Result = _variant_t("");
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
在ASP网页中:
(ASP)
Response.write MyADOObject.ADOMakeWebTable("SELECT * FROM some_table")
这样在你的网页中就有了一个表,我们经常使用它。实际上,我们还有一个方法改变表格中的参数:
STDMETHODIMP CADO::ADOSetWebTableInfo(int Border, BSTR FGColor, BSTR BGColor)
{
// This is where we set up the variables for the WEB tables
if(Border <0 || Border > 5)
m_Border = 0;
else
m_Border = Border;
m_FGColor = FGColor;
m_BGColor = BGColor;
return S_OK;
}
这个方法允许我们改变表格的边框大小,前景和背景颜色。你很容易加入更多的选择。
下面这个方法是存储程序版:
STDMETHODIMP CADO::ADOMakeSPWebTable(BSTR SPName, VARIANT ParamArray, VARIANT *Result)
{
VARIANT RecordsEffected;
RecordsEffected.vt = VT_INT;
BSTR bstrResult = NULL;
std::strstream Holder;
*Result = _variant_t("");
try
{
_RecordsetPtr PopSet;
PopSet.CreateInstance(__uuidof(Recordset));
PopSet->CursorLocation = adUseClient;
if(m_Connection->State != adStateOpen)
{
*Result = _variant_t("VISP_COM_ERROR_CONNECTION_NOT_OPEN");
return S_FALSE;
}
// Now set up the command object
m_Command->ActiveConnection = m_Connection;
// enter the SP name
m_Command->CommandText = _bstr_t(SPName);
// Now execute it
PopSet = m_Command->Execute(&RecordsEffected,&ParamArray,adCmdStoredProc);
if(!PopSet->adoEOF)
{
_bstr_t btColDelim(L"</TD><TD>");
_bstr_t btRowDelim(L"</TD</TR><TR><TD>");
_bstr_t btNullExp(L"");
PopSet->raw_GetString(adClipString,-1,btColDelim,btRowDelim,btNullExp,&bstrResult);
Holder.str("");
Holder<<"<TABLE BORDER="<<m_Border<<" BGCOLOR="<<(char*)m_BGColor<<" STYLE='color="<<(char*)m_FGColor<<"'><TR><TD>"<<(char*)_bstr_t(bstrResult)<<"</TD></TR></TABLE>";
*Result = _variant_t(_bstr_t(Holder.str().c_str()));
}
else
*Result = _variant_t("");
}
catch(_com_error &e){} // error handling here
catch(...){}// all other exceptions here
return S_OK;
}
当然,你可以包封成专用方法,像:
STDMETHODIMP CADO::GetEmployeeTable(VARIANT *Result)
{
ADOMakeSPWebTable(_bstr_t("EmployeeList"),m_vNull,Result);
return S_OK;
}
在ASP网页中:
(ASP)
Response.Write MyADOObject.GetEmployeeTable()
结论:
如果你已经读到了这里,我希望本文能对你有所帮助。我希望读过本文后,你使用ADO的时候会容易一些。我希望有更多关于ADO的书,也许我会献丑写一本。但在此之前,我希望你喜欢ADO和ATL。一旦你理解了它们是怎样工作的,你会发现实际上它们并不差。祝你好运!
(完)