自定义 CRecordset class 在指定 useMultiRowFetch 时不调用 DoFieldExchange()

Custom CRecordset class does not call DoFieldExchange() when useMultiRowFetch is specified

我实现了一个自定义 CRecordset class,代码类似于以下内容:

ASSERT(prs->GetRowsetSize() == 25);
while (!prs->IsEOF())
{
    for (int i = 1; i <= prs->GetRowsFetched(); i++)
    {
        prs->SetRowsetCursorPosition((WORD)i);

        // Inspecting data here...

    }
    prs->MoveNext();
}
prs->Close();

显然,当使用多行提取时,CRecordset 不会像不使用多行提取时那样调用我的 DoFieldExchange 覆盖,这是设计使然。所以我的数据不会自动填充。那么问题是如何获取数据?

答案似乎是调用 GetFieldValue()。但是当我这样做时,我得到一个 Invalid cursor position 错误! (GetFieldValue() 在我不使用多行提取时工作正常。)

下面是我的记录集的精简版class。此外,@EylM 足以在下面的答案中创建一个示例,他说 does 对他有用。但是,当我完全复制他的代码并更改连接和查询我的数据库所需的内容时,我 still 得到一个 Invalid cursor position when我叫GetFieldValue().

我不知道还有什么不同。我看到他在使用 MySQL 而我在使用 SQL 服务器。但肯定 CRecordset 与 SQL 服务器一起工作。我也尝试了所有可用的 SQL 服务器 ODBC 驱动程序,但结果总是一样。

class CRS : public CRecordset
{
public:
    // Data variables
    int m_nId;
    TCHAR m_szName[CUSTOMER_NAME_MAXLENGTH + 1];

    // Bulk data variables
    int* m_pnIds;
    long* m_pnIdLengths;
    LPTSTR m_pszNames;
    long* m_pnNameLengths;

    // Constructor
    CRS(CDatabase* pDatabase = NULL)
        : CRecordset(pDatabase)
    {
        m_nFields = 2;

        m_nId = 0;
        m_szName[0] = '[=12=]';

        m_pnIds = NULL;
        m_pnIdLengths = NULL;
        m_pszNames = NULL;
        m_pnNameLengths = NULL;
    }

    CString GetDefaultSQL()
    {
        return CCustomerData::m_szTableName;
    }

    // This method is never called when
    // CRecordset::useMultiRowFetch is specified!
    void DoFieldExchange(CFieldExchange* pFX)
    {
        pFX->SetFieldType(CFieldExchange::outputColumn);
        RFX_Int(pFX, _T("Id"), m_nId);
        RFX_Text(pFX, _T("Name"), m_szName, CUSTOMER_NAME_MAXLENGTH);
    }

    // This method is called several times
    void DoBulkFieldExchange(CFieldExchange* pFX)
    {
        pFX->SetFieldType(CFieldExchange::outputColumn);
        RFX_Int_Bulk(pFX, _T("Id"), &m_pnIds, &m_pnIdLengths);
        RFX_Text_Bulk(pFX, _T("Name"), &m_pszNames, &m_pnNameLengths, (CUSTOMER_NAME_MAXLENGTH + 1) * 2);
    }
};

更新:

花更多时间在这上面,我已经能够编写直接从行集数据读取数据的代码(在我的例子中,来自 m_pnIdsm_pnIdLengthsm_pszNamesm_pnNameLengths)。也许这就是我需要采取的方法。

但问题依然存在。为什么我不能在 SQL 服务器数据库上使用 GetFieldValue()SetRowsetCursorPosition() 有什么意义?

来自 CRecordset::DoFieldExchange 的文档:

When bulk row fetching is not implemented, the framework calls this member function to automatically exchange data between the field data members of your recordset object and the corresponding columns of the current record on the data source.

DoFieldExchange 仅在 Open 函数中未指定 CRecordset::useMultiRowFetch 时调用。 使用 VS 2019 (14.22.27905) 查看 MFC 代码 CRecordset::BindFieldsToColumns、dbcore.cpp:

// Binding depends on fetch type
if (m_dwOptions & useMultiRowFetch)
    DoBulkFieldExchange(&fx);
else
    DoFieldExchange(&fx);

听起来你的这种行为是设计使然的。

编辑:

这是多行提取的工作示例。成功的方法是在开始标志中 CRecordset::useExtendedFetch

数据库: 我将 MySQL 与一个简单的 table 结合使用,其中包含 2 列。这是创建脚本。

CREATE TABLE `categories` (
  `CatID` int(11) NOT NULL,
  `Category` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`CatID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

MFC:

CMultiRowSet.h
class CMultiRowSet : public CRecordset
{   
public:
    CMultiRowSet(CDatabase* pDB);

    virtual void DoBulkFieldExchange(CFieldExchange* pFX);

    // Field/Param Data
    // field data members
    long* m_rgID;
    LPSTR m_rgName;

    // pointers for the lengths
    // of the field data
    long* m_rgIDLengths;
    long* m_rgNameLengths;
};

CMultiRowSet.cpp
void CMultiRowSet::DoBulkFieldExchange(CFieldExchange* pFX)
{
    // call the Bulk RFX functions
    // for field data members
    pFX->SetFieldType(CFieldExchange::outputColumn);
    RFX_Long_Bulk(pFX, _T("[CatID]"),
        &m_rgID, &m_rgIDLengths);
    RFX_Text_Bulk(pFX, _T("[Category]"),
        &m_rgName, &m_rgNameLengths, 30);
}

用法:

CDatabase database; 
CString sCatID, sCategory;

TRY
{
    CString connStr = (_T("Driver={MySQL ODBC 8.0 Unicode Driver};Server=localhost;Database=XXXX;User=XXX; Password=XXXX; Option = 3;"));

    // Open the database
    database.OpenEx(connStr,CDatabase::noOdbcDialog);

    // Allocate the recordset
    CMultiRowSet recset(&database);     

    // Execute the query
    // make sure you use CRecordset::useExtendedFetch.
    recset.Open(CRecordset::forwardOnly, _T("SELECT CatID, Category FROM Categories"), CRecordset::readOnly|CRecordset::useMultiRowFetch|CRecordset::useExtendedFetch);

    // Loop through each record
    while (!recset.IsEOF())
    {       
        // The default `GetRowsetSize` is 25. I have 4 rows in my database. 
        // GetRowsFetched returns 4 in my case.
        for (int rowCount = 1; rowCount <= (int)recset.GetRowsFetched(); rowCount++)
        {           
            recset.SetRowsetCursorPosition(rowCount);

            // Copy each column into a variable
            recset.GetFieldValue(_T("CatID"), sCatID);
            recset.GetFieldValue(_T("Category"), sCategory);
        }

        // goto next record
        recset.MoveNext();
    }

    recset.Close();

    // Close the database
    database.Close();
}
CATCH(CDBException, e)
{
    // If a database exception occured, show error msg
    AfxMessageBox(_T("Database error: ") + e->m_strError);
}
END_CATCH;