Я экспериментирую с программированием базы данных OLE с использованием Microsoft ATL и C ++ с Visual Studio 2005. Я создал образец консольной программы Windows вместе с простой базой данных с одной таблицей, содержащей два столбца, идентификатор из 10 символов (числовые цифры ANSI) и подписанный 32-битный отсчет В таблице две строки. База данных, таблица и строки были созданы с помощью SQL Server 2005 Server Management Studio Express.
Мне нужно знать метод, используемый с ATL для обновления данных в строке, которая была выбрана.
Возможно, я неправильно делаю выборку, что может быть проблемой. Во время выполнения программы я вижу три ASSERT, которые, кажется, связаны с вызовом метода GetInterfacePtr()
что вызывается myTable.Open ()
при выполнении SQL-запросов для получения строк. Однако возвращаемое значение HRESULT — S_OK, и SQL-запрос, кажется, работает правильно.
HRESULT
от myTable.SetData()
имеет значение 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
Edit # 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 stackoverflow 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();
}
};
Вы, вероятно, открываете таблицу, не запрашивая возможность изменять / обновлять данные. Без запроса этого у вас нет интерфейсов, используемых 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));
}
В опубликованном вопросе есть несколько проблем с оригинальной программой. Модифицированная версия с полученным результатом представлена ниже.
Первой проблемой была проблема, обозначенная Романом Р. выше, в которой свойства для Open()
были установлены, чтобы разрешить только чтение строк из базы данных. Как он объясняет, свойства должны быть добавлены, чтобы позволить обновление данных.
OpenAll()
метод использует GetRowsetProperties()
метод CTable_1Accessor
класс для установки свойств как часть обработки открытия базы данных. Это можно увидеть, установив точку останова отладчика в методе. Однако другие звонки на Open()
метод не вызывать GetRowsetProperties()
функционировать так, что эти другие вызовы Open()
должен иметь набор свойств, указанный для изменения свойств по умолчанию для результирующего набора строк. Так что не только изменения были необходимы для GetRowsetProperties()
метод CTable_1Accessor
класс в мастере сгенерированный включаемый файл, также необходимо добавить соответствующие свойства для каждого Open()
для того, чтобы разрешить использование SetDate()
метод для обновления набора строк.
Вторая проблема с приведенным примером заключается в том, что Close()
Функция не используется, чтобы закрыть полученный набор строк после использования любого из различных Open()
вариации. Отсутствие Close()
это то, что вызывало утверждение, когда второй и более поздние Open()
были позваны.
Модифицированный источник ниже. Он был создан путем запуска нового проекта консоли Windows с ATL в Visual Studio 2005, а затем с помощью мастера классов для создания класса доступа к таблице на основе ATL. С помощью мастера я установил флажки в диалоговом окне мастера, чтобы разрешить вставку, удаление и обновление, которые затем создали правильные GetRowsetProperties()
метод, описанный Романом Р. для Accessor, используемого 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