I’ve begun maintaining an MFC desktop app that uses classic ADO. There’s a database access dll that wraps the imported ADO which is used throughout the app. Stored procedures are used, but also there are a lot of text queries, none of which are parameterized. I’ve been asked to convert them to parameterized queries. The first 2 queries I have altered have run into a problem. I’m calling a function twice which gets a string value from a table called “Parameters” (no relation) indexed by a unique integer. I create an ADO parameter to hold the integer & perform the query. The 2nd time this is called, the parameter is not being set & returns -1 (seen in SQL profiler, the first time it is correct) resulting in an empty recordset.
I’m at a loss to explain this, (although from past experience I’m missing something obvious), it seems the likely explanation is that the parameter creation or clearing code I’ve inherited is wrong(but I’m probably wrong about that as well 🙂 – here’s some example code:
m_spGlobal->LoginType = CSTR GetParam(19006); //Calls function below
BOOL bPrompt = (GetParam(19007) == "1") ? TRUE:FALSE; //Second call
CString CMainFrame::GetParam(int nPram )
{
DBSWIFTLib::IDBRecordsetPtr pDB(__uuidof(DBSWIFTLib::DBRecordset));//Declare smart pointer to dB access dll
CString sSQL;
pDB->ClearParameters();// loops through parameters collection, deleting any that exist(but shouldn't be any)
pDB->ParameterInt( "pPram",nPram, ADODB::adParamInput);
pDB->LockType = ADODB::adLockReadOnly;
pDB->CursorLocation = ADODB::adUseClient;
pDB->CursorType = ADODB::adOpenForwardOnly;
sSQL.Format("SELECT sValue FROM Parameters WITH (NOLOCK) WHERE Id= ?");
pDB->ExecuteSQL(CSTR sSQL);
CString sReturn = "";
if (!pDB->Empty())
{
sReturn = pDB->strval["sValue"];
}
return sReturn.Trim();
}
// ExecuteSQL from above function(in different COM dll)
STDMETHODIMP CDBRecordset::ExecuteSQL(LPSTR pszCommand)
{
try
{
if (m_pRs->State != adStateClosed )
m_pRs->Close( );
m_pCmd->CommandText = pszCommand;
m_pCmd->CommandType = adCmdText;
m_pCmd->PutActiveConnection(_variant_t((IDispatch* )m_pRsConn ) );
m_pRs = m_pCmd->Execute(&vtMissing, &vtMissing, adCmdText );
}
catch (_com_error &e)
{
m_strFunction = _T("ExecuteSQL");
m_strExtraInfo = pszCommand;
DisplayADOError(e );
}
return S_OK;
}
//In same dll as previous function
STDMETHODIMP CDBRecordset::ParameterInt(LPSTR pszName, int nValue, int nDirection )
{
try
{
m_pParam = m_pCmd->CreateParameter(pszName,
adInteger,
(ParameterDirectionEnum )nDirection,
sizeof(nValue ),
(long )nValue );
m_pCmd->Parameters->Append(m_pParam );
}
catch (_com_error &e )
{
m_strFunction = _T("ParameterInt");
m_strExtraInfo = pszName;
DisplayADOError(e );
}
return S_OK;
}
STDMETHODIMP CDBRecordset::ClearParameters( )
{
try
{
long cParams = (m_pCmd->Parameters->Count - 1 );
for (long m_nParams = cParams; m_nParams >= 0; m_nParams-- )
m_pCmd->Parameters->Delete(variant_t(m_nParams ) );
}
catch (_com_error &e )
{
m_strFunction = _T("ClearParameters");
DisplayADOError(e );
}
return S_OK;
}
-
Further (strange) information: I have now noticed that either the existence or the execution of the two function calls to “GetParams” cause some other methods in the data access dll to throw _com_errors (they involve copying a recordset) when they are called from a unrelated dll. If I comment out the 2 function calls, the errors disappear, with no change to the methods throwing the error.
-
Edit 21:31 4/11/11
I should have given the implementation of ClearParameters – see bottom of above code
As far as parameters not being set,what I meant was that the value substituted in the query was “-1” rather than “19007” according to the SQL Server Profiler recording.I think -1 is a spurious value that comes from the parameter not being assigned a value successfully when it’s created.
I should add that the ADO Command & Recordset objects are created in the DBRecordSet constructor.-
EDIT 21:32 5/11/11
I may have solved the problem. I’m using the “backcompat” version of ADO,I’m not sure if method parameter types have altered since the app was written in VS6 but the signature of CreateParameter is now:_bstr_t Name, enum DataTypeEnum Type, enum ParameterDirectionEnum Direction, ADO_LONGPTR Size, const _variant_t & Value
-
which differs from the parameters in the example code(Name is an LPSTR,size for an integer parameter in the MSDN e.g. is -1, value is being passed as a long, not a variant) Also in the MSDN example the Parameter value is set again after the Create(not sure this is relevant though, why set it twice?unless this is a known “quirk”). Making the types conform to the ADO object model seems to have done the trick.
I shall post this as an answer if testing verifies that everything works correctly
As I wrote in the Edit above, I discovered that although the app was working prior to the introduction of parameterised queries(i.e. all the stored procedure data access calls worked, the implicit casts seemingly OK for SPs’s), the types being used in the ADO method parameters for “CreateParameter” weren’t correct( a long instead of a variant_t, etc.) resulting in “undefined” behaviour.
Once I corrected the method parameter types , the parameterised queries detailed above(& others) worked fine, & the other seemingly unrelated errors vanished also.
I missed this at first because the app had been working for 10 years+ without parameterised queries – it took their addition to expose the problem.
(I’ll wait a little while before accepting this as an answer, in case anyone has a better explanation of this)
EDIT – While I got them working for Read-only recordsets, I’ve found that I get a “provider does not support updating” error when using the technique of creating a connection and opening a transaction from th connection object, and using this connection to open several subsequent recordsets,altering various values, issuing “Update” commands and committing the Transaction. As soon as I try to amend a field’s value, I get the error. This never happens with recordsets opened(from recordset object rather than command) or Stored procs creating a recordset. The connection’s location is ‘Client’, I’ve no reason to expect it not to work. I’ve observed it in the SQL Profiler and everything looks correct(I think) up to the moment it doesn’t work.