如何更新使用 C++ 中的 ATL OLE 数据库从简单的 SQL 服务器表中获取的行数据
Posted
技术标签:
【中文标题】如何更新使用 C++ 中的 ATL OLE 数据库从简单的 SQL 服务器表中获取的行数据【英文标题】:how to update row data fetched using ATL OLE database in C++ from simple SQL server table 【发布时间】:2014-06-11 22:57:10 【问题描述】:我正在尝试使用 Microsoft ATL 和 C++ 以及 Visual Studio 2005 进行 OLE 数据库编程。我创建了一个示例 Windows 控制台程序以及一个简单的数据库,其中包含一个包含两列的单个表,一个 10 个字符(ANSI 数字)id和一个有符号的 32 位长计数。表中有两行。数据库、表和行是使用 SQL Server 2005 Server Management Studio Express 创建的。
我需要知道与 ATL 一起使用的方法来更新已获取的行中的数据。
我可能不正确地进行提取,这可能是问题所在。在程序执行期间,我看到三个 ASSERT,它们似乎涉及对方法 GetInterfacePtr()
的调用,该方法在执行用于检索行的 SQL 查询时由 myTable.Open ()
调用。然而,返回的 HRESULT 值是 S_OK 并且 SQL 查询似乎工作正常。
来自myTable.SetData()
的HRESULT
根据调试器的值为E_NOINTERFACE
,这是一个错误返回,并且选定的行没有被更新但是我不知道这意味着什么,也不知道需要进行哪些更改更新行。
Table_1.h 包含文件由 Visual Studio 2005 中的向导生成,但该类派生自 CCommand 类型的模板。
class CTable_1 : public CCommand<CAccessor<CTable_1Accessor> >
主程序源码为:
#include "stdafx.h"
#include <string>
#include <iostream>
// the VS 2005 generated include file for the table.
#include "Table_1.h"
int _tmain(int argc, _TCHAR* argv[])
HRESULT hrResult = OleInitialize(NULL);
switch (hrResult)
case S_OK:
break;
default:
std::cout << "Ole Initialization Failed " << hrResult << std::endl;
return FALSE;
CTable_1 myTable;
HRESULT hr = myTable.OpenAll ();
std::string m_strQuery("select * from Table_1");
hrResult = myTable.Open(myTable.m_session, m_strQuery.c_str());
if (hrResult == S_OK)
int nItem = 0;
while (myTable.MoveNext() == S_OK)
if (nItem < 25)
char szValueChar[40];
for (int i = 0; i < 40; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
std::cout << nItem << " -> " << sTemp << " : " << myTable.m_Count << std::endl;
nItem++;
else
std::cout << "assessor open failed " << hrResult << " \"" << m_strQuery << "\"" << std::endl;
// Update a specific row in the table by incrementing its count.
m_strQuery = "select * from Table_1 where IdNumber='0000000001'";
hrResult = myTable.Open(myTable.m_session, m_strQuery.c_str());
if (hrResult == S_OK)
hrResult = myTable.MoveNext();
char szValueChar[40];
for (int i = 0; i < 40; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
std::cout << " selected item -> " << sTemp << " : " << myTable.m_Count << std::endl;
myTable.m_Count++;
// <<<<< Following SetData() returns HRESULT of E_NOINTERFACE
hrResult = myTable.SetData ();
if (hrResult != S_OK) std::cout << "** update error. hrResult = " << hrResult << std::endl;
std::cout << std::endl << " after update" << std::endl;
m_strQuery = "select * from Table_1";
hrResult = myTable.Open(myTable.m_session, m_strQuery.c_str());
if (hrResult == S_OK)
int nItem=0;
while (myTable.MoveNext() == S_OK)
if (nItem < 25)
char szValueChar[40];
for (int i = 0; i < 40; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
std::cout << nItem << " -> " << sTemp << " : " << myTable.m_Count << std::endl;
nItem++;
else
std::cout << "assessor open failed " << hrResult << " \"" << m_strQuery << "\"" << std::endl;
myTable.CloseAll ();
OleUninitialize ();
return 0;
控制台窗口的输出如下所示:
0 -> 0000000001 : 2
1 -> 0000000002 : 3
selected item -> 0000000001 : 2
** update error. hrResult = -2147467262
after update
0 -> 0000000001 : 2
1 -> 0000000002 : 3
编辑 #1 - Table_1.h(根据建议的更改生成的向导)
// Table_1.h : Declaration of the CTable_1
#pragma once
// code generated on Friday, April 25, 2014, 10:25 PM
class CTable_1Accessor
public:
TCHAR m_IdNumber[11];
LONG m_Count;
// The following wizard-generated data members contain status
// values for the corresponding fields in the column map. You
// can use these values to hold NULL values that the database
// returns or to hold error information when the compiler returns
// errors. See Field Status Data Members in Wizard-Generated
// Accessors in the Visual C++ documentation for more information
// on using these fields.
// NOTE: You must initialize these fields before setting/inserting data!
DBSTATUS m_dwIdNumberStatus;
DBSTATUS m_dwCountStatus;
// The following wizard-generated data members contain length
// values for the corresponding fields in the column map.
// NOTE: For variable-length columns, you must initialize these
// fields before setting/inserting data!
DBLENGTH m_dwIdNumberLength;
DBLENGTH m_dwCountLength;
void GetRowsetProperties(CDBPropSet* pPropSet)
pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
//<< lines added from *** answer
pPropSet->AddProperty(DBPROP_IRowsetChange, (bool) TRUE);
pPropSet->AddProperty(DBPROP_IRowsetUpdate, (bool) TRUE);
pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT);
HRESULT OpenDataSource()
CDataSource _db;
HRESULT hr;
hr = _db.OpenFromInitializationString(L"Provider=SQLNCLI.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TestData;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=CIT-31204E1FF03;Use Encryption for Data=False;Tag with column collation when possible=False;MARS Connection=False;DataTypeCompatibility=0;Trust Server Certificate=False");
if (FAILED(hr))
#ifdef _DEBUG
AtlTraceErrorRecords(hr);
#endif
return hr;
return m_session.Open(_db);
void CloseDataSource()
m_session.Close();
operator const CSession&()
return m_session;
CSession m_session;
DEFINE_COMMAND_EX(CTable_1Accessor, L" \
SELECT \
IdNumber, \
Count \
FROM dbo.Table_1")
// In order to fix several issues with some providers, the code below may bind
// columns in a different order than reported by the provider
BEGIN_COLUMN_MAP(CTable_1Accessor)
COLUMN_ENTRY_LENGTH_STATUS(1, m_IdNumber, m_dwIdNumberLength, m_dwIdNumberStatus)
COLUMN_ENTRY_LENGTH_STATUS(2, m_Count, m_dwCountLength, m_dwCountStatus)
END_COLUMN_MAP()
;
class CTable_1 : public CCommand<CAccessor<CTable_1Accessor> >
public:
HRESULT OpenAll()
HRESULT hr;
hr = OpenDataSource();
if (FAILED(hr))
return hr;
__if_exists(GetRowsetProperties)
CDBPropSet propset(DBPROPSET_ROWSET);
__if_exists(HasBookmark)
if( HasBookmark() )
propset.AddProperty(DBPROP_IRowsetLocate, true);
GetRowsetProperties(&propset);
return OpenRowset(&propset);
__if_not_exists(GetRowsetProperties)
__if_exists(HasBookmark)
if( HasBookmark() )
CDBPropSet propset(DBPROPSET_ROWSET);
propset.AddProperty(DBPROP_IRowsetLocate, true);
return OpenRowset(&propset);
return OpenRowset();
HRESULT OpenRowset(DBPROPSET *pPropSet = NULL)
HRESULT hr = Open(m_session, NULL, pPropSet);
#ifdef _DEBUG
if(FAILED(hr))
AtlTraceErrorRecords(hr);
#endif
return hr;
void CloseAll()
Close();
ReleaseCommand();
CloseDataSource();
;
【问题讨论】:
【参考方案1】:您可能在没有请求更改/更新数据的能力的情况下打开表。如果没有请求,您就没有 SetData
调用使用的接口,因此会出现错误。在你的桌子课上你应该有类似的东西(这应该放在Table_1.h
你没有发布的某个地方):
VOID GetRowsetProperties(CDBPropSet* pSet) throw()
ATLVERIFY(pSet->AddProperty(DBPROP_IRowsetChange, (bool) TRUE));
ATLVERIFY(pSet->AddProperty(DBPROP_IRowsetUpdate, (bool) TRUE));
ATLVERIFY(pSet->AddProperty(DBPROP_UPDATABILITY,
DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT));
【讨论】:
感谢您的回答。在我将要添加到问题中的 Table_1.h 中,访问器中有一个GetRowsetProperties()
函数,我在其中添加了您建议的源代码行,但是我仍然从 SetData() 获得相同的 HRESULT。我不知道_W
做了什么,我想了解更多信息。使用它会产生编译错误,谷歌没有发现任何有用的东西。
我建议进入这个E_NOINTERFACE
的来源,找出究竟是什么接口找不到。 _W
是 ATLVERIFY
结果检查宏的缩写。
遍历我发现CComPtr<IRowsetChange> m_spRowsetChange;
,CRowSet
类中的一个变量是 NULL 而不是有效值。此指针在SetData()
中用于执行实际的行更新。这表明我需要做一些我没有做的操作。有什么想法吗?
据我记得,您只是按名称打开 [可更新] 表,而不是通过 SQL 查询。您从查询中获得的结果可能无法按设计进行更新。所以你得到了数据,但即使你请求它,你也没有更新界面。
我重新开始了一个干净的项目,一次添加一点。我发现如果我使用OpenAll()
,然后使用MoveFirst()
选择第一行,我可以在该行上执行SetData()
并更新计数。那么如何选择特定行并更新该特定行?【参考方案2】:
在发布的问题中,原始程序存在几个问题。下面提供了带有输出的修改版本。
第一个问题是上面 Roman R. 确定的问题,其中Open()
的属性设置为只允许从数据库中读取行。正如他解释的那样,需要添加属性才能更新数据。
OpenAll()
方法使用CTable_1Accessor
类的GetRowsetProperties()
方法来设置属性作为处理数据库打开的一部分。这可以通过在方法中设置调试器断点来查看。但是,对Open()
方法的其他调用不会调用GetRowsetProperties()
函数,因此对Open()
的这些其他调用必须指定一个属性集,以便修改结果行集的默认属性。因此,不仅需要更改向导生成的包含文件中的CTable_1Accessor
类的GetRowsetProperties()
方法,还需要为每个Open()
添加适当的属性,以便允许使用SetDate()
方法更新行集。
发布示例的第二个问题是Close()
函数在使用各种Open()
变体后没有用于关闭检索到的行集。在调用第二个和以后的Open()
时,缺少Close()
是导致断言的原因。
修改后的来源如下。这是通过在 Visual Studio 2005 中使用 ATL 启动一个新的 Windows 控制台项目,然后使用类向导创建基于表访问 ATL 的类来创建的。使用向导,我检查了向导对话框中的框以允许插入、删除和更新,然后创建正确的 GetRowsetProperties()
方法,如 Roman R. 所述,用于 CTable1
类使用的访问器 (class CTable_1 : public CCommand<CAccessor<CTable_1Accessor> >
) .
修改后的示例程序如下。
#include "stdafx.h"
#include <string>
#include <iostream>
#include "Table_1.h"
int _tmain(int argc, _TCHAR* argv[])
HRESULT hrResult = OleInitialize(NULL);
switch (hrResult)
case S_OK:
break;
default:
std::cout << "Ole Initialization Failed " << hrResult << std::endl;
return 1;
// Our standard property set used in various Open() where we specify an SQL query.
CDBPropSet pPropSet(DBPROPSET_ROWSET);
pPropSet.AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
pPropSet.AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL);
pPropSet.AddProperty(DBPROP_IGetRow, true, DBPROPOPTIONS_OPTIONAL);
pPropSet.AddProperty(DBPROP_IRowsetChange, true, DBPROPOPTIONS_OPTIONAL);
pPropSet.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);
CTable_1 myTable; // our table access object we will use for all our operations.
int nItem = 0;
HRESULT hr;
// First example, OpenAll() which uses the default SQL query retrieving all records
hr = myTable.OpenAll ();
for (nItem = 0, hr = myTable.MoveFirst(); hr == S_OK; hr = myTable.MoveNext())
char szValueChar[12] = 0;
for (int i = 0; i < 10; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
std::cout << nItem << " -> " << sTemp << " : " << myTable.m_Count << std::endl;
nItem++;
std::cout << " -- Update the first record then redisplay rows" << std::endl;
hr = myTable.MoveFirst (); // move to the first row of the row set
myTable.m_Count++; // increment the count of the first row
hr = myTable.SetData (); // update the database with the modified count
// redisplay all rows including the updated row.
for (nItem = 0, hr = myTable.MoveFirst(); hr == S_OK; hr = myTable.MoveNext())
char szValueChar[12] = 0;
for (int i = 0; i < 10; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
std::cout << nItem << " -> " << sTemp << " : " << myTable.m_Count << std::endl;
nItem++;
myTable.Close(); // close the OpenAll() so that we can now do Open() with SQL query.
std::cout << " -- update record specific row" << std::endl;
TCHAR *tsSqlQuery = _T("select * from dbo.Table_1 where IdNumber='0000000002'");
hr = myTable.Open (myTable.m_session, tsSqlQuery, &pPropSet);
if ((hr = myTable.MoveFirst()) == S_OK)
char szValueChar[12] = 0;
for (int i = 0; i < 10; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
LONG countTemp = myTable.m_Count;
myTable.m_Count++;
std::cout << " incrementing " << sTemp << " " << countTemp << " to " << myTable.m_Count << std::endl;
hr = myTable.SetData ();
myTable.Close(); // close this row set.
// select all rows and output the values after the various changes
// do an Open() with our new query using our standard property set.
tsSqlQuery = _T("select * from dbo.Table_1");
hr = myTable.Open (myTable.m_session, tsSqlQuery, &pPropSet);
nItem = 0;
for (nItem = 0, hr = myTable.MoveFirst(); hr == S_OK; hr = myTable.MoveNext())
char szValueChar[12] = 0;
for (int i = 0; i < 10; i++) szValueChar[i] = (char)myTable.m_IdNumber[i];
std::string sTemp (szValueChar);
std::cout << nItem << " -> " << sTemp << " : " << myTable.m_Count << std::endl;
nItem++;
myTable.Close(); // close this row set.
OleUninitialize ();
return 0;
上述程序产生的输出如下。此输出显示原始行值以及对某些行值进行若干不同更改的结果。
0 -> 0000000001 : 22
1 -> 0000000002 : 12
2 -> 0000000004 : 23
3 -> 0000000006 : 34
-- Update the first record then redisplay rows
0 -> 0000000001 : 23
1 -> 0000000002 : 12
2 -> 0000000004 : 23
3 -> 0000000006 : 34
-- update record specific row
incrementing 0000000002 12 to 13
0 -> 0000000001 : 23
1 -> 0000000002 : 13
2 -> 0000000004 : 23
3 -> 0000000006 : 34
【讨论】:
另请参阅此示例以了解 SQL Server Compact Edition ***.com/questions/23459657/…以上是关于如何更新使用 C++ 中的 ATL OLE 数据库从简单的 SQL 服务器表中获取的行数据的主要内容,如果未能解决你的问题,请参考以下文章
如何将 WTL 和 ATL 添加到 Visual Studio C++ Express 2008