自定义 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_pnIds
、m_pnIdLengths
、m_pszNames
和 m_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;
我实现了一个自定义 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_pnIds
、m_pnIdLengths
、m_pszNames
和 m_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;