无法让经典 ADO 参数化文本查询在旧版 MFC 应用程序中工作

发布于 2024-12-13 14:55:28 字数 3857 浏览 1 评论 0原文

我已经开始维护一个使用经典 ADO 的 MFC 桌面应用程序。有一个数据库访问 dll,它包装了在整个应用程序中使用的导入的 ADO。使用了存储过程,但是也有很多文本查询,没有一个是参数化的。我被要求将它们转换为参数化查询。我更改的前两个查询遇到了问题。我调用一个函数两次,该函数从一个名为“参数”(无关系)的表中获取一个字符串值,该表由唯一整数索引。我创建一个 ADO 参数来保存整数 &执行查询。第二次调用时,参数未设置且未设置。返回 -1(在 SQL 探查器中看到,第一次是正确的),导致空记录集。 我无法解释这一点,(尽管从过去的经验来看,我遗漏了一些明显的东西),似乎可能的解释是我继承的参数创建或清除代码是错误的(但我可能错了这也是:-) - 这是一些示例代码:

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;
}
  • 进一步(奇怪)信息:我现在注意到对“GetParams”的两个函数调用的存在或执行会导致数据访问dll中的一些其他方法扔当从不相关的 dll 调用它们时,会出现 _com_errors(它们涉及复制记录集)。如果我注释掉这两个函数调用,错误就会消失,而抛出错误的方法不会发生任何变化。

  • 编辑 21:31 2011 年 4 月 11 日 我应该给出 ClearParameters 的实现 - 请参阅上面代码的底部 至于未设置参数,我的意思是根据 SQL Server Profiler 记录,查询中替换的值是“-1”而不是“19007”。我认为 -1 是来自参数的虚假值创建时未成功赋值。 我应该补充一点,ADO Command & Recordset 对象是在 DBRecordSet 构造函数中创建的。

    • 编辑 21:32 5/11/2011 我可能已经解决了问题。我正在使用 ADO 的“backcompat”版本,我不确定自应用程序是在 VS6 中编写以来方法参数类型是否已更改,但 CreateParameter 的签名现在是:

      _bstr_t 名称、枚举 DataTypeEnum 类型、枚举 ParameterDirectionEnum 方向、ADO_LONGPTR 大小、const _variant_t &价值
      

这与示例代码中的参数不同(名称是LPSTR,MSDN 中整数参数的大小,例如 -1,值作为长整数传递,而不是变体)此外,在 MSDN 示例中,参数值在创建之后再次设置(尽管不确定这是否相关,为什么要设置它两次?除非这是一个已知的“怪癖”)。使类型符合 ADO 对象模型似乎已经成功了。 如果测试验证一切正常,我将将此作为答案发布

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

抠脚大汉 2024-12-20 14:55:28

正如我在上面的编辑中所写,我发现虽然应用程序在引入参数化查询之前就可以工作(即所有存储过程数据访问调用都工作,隐式转换对于 SP 来说似乎没问题),但 ADO 中使用的类型“CreateParameter”的方法参数不正确(long 而不是variant_t 等),导致“未定义”行为。
一旦我更正了方法参数类型,上面详述的参数化查询(以及其他)就可以正常工作,并且可以正常工作。其他看似无关的错误也消失了。
我一开始错过了这一点,因为该应用程序已经运行了 10 年以上,没有参数化查询 - 直到他们的添加才暴露了问题。
(我会等一会儿,然后再接受这个答案,以防有人对此有更好的解释)

编辑 - 当我让他们为只读记录集工作时,我发现当使用创建连接并从连接对象打开事务的技术,并使用此连接打开多个后续记录集、更改各种值、发出“更新”命令并提交时,我收到“提供程序不支持更新”错误交易。一旦我尝试修改字段的值,就会收到错误。打开记录集(从记录集对象而不是命令)或创建记录集的存储过程永远不会发生这种情况。连接的位置是“客户端”,我没有理由期望它不起作用。我在 SQL Profiler 中观察到它,直到它不起作用的那一刻,一切看起来都是正确的(我认为)。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文