how to update row data fetched using ATL OLE database in C++ from simple SQL server table

StackOverflow https://stackoverflow.com/questions/23306475

  •  09-07-2023
  •  | 
  •  

Pergunta

I am experimenting with OLE database programming using Microsoft ATL and C++ with Visual Studio 2005. I have created a sample Windows console program along with a simple database with a single table containing two columns, a 10 character (ANSI numeric digits) id and a signed 32 bit long count. There are two rows in the table. The database, table, and rows were created with SQL Server 2005 Server Management Studio Express.

I need to know the method used with ATL to update the data in a row that has been fetched.

I may be doing the fetch improperly which may be the problem. During program execution I am seeing three ASSERTs which seem to involve a call to a method GetInterfacePtr() that is being invoked by the myTable.Open () when executing the SQL queries for retrieving rows. However the HRESULT value returned is S_OK and the SQL query appears to be working properly.

The HRESULT from the myTable.SetData() has a value of E_NOINTERFACE according to the debugger which is an error return and the selected row is not being updated however I do not know what that means nor what changes are needed to update the row.

The Table_1.h include file is generated by a wizard in Visual Studio 2005 however the class is derived from a CCommand type of template.

class CTable_1 : public CCommand<CAccessor<CTable_1Accessor> >

The main program source is:

#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;
}

The output of the console window looks like the following:

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 (wizard generated with suggested change)

// 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();
    }
};
Foi útil?

Solução 2

There are several problems with the original program in the question posted. A modified version with the output produced is provided below.

The first problem was the problem identified by Roman R. above in which the properties for the Open() were set to allow only reading of rows from the database. As he explains properties needed to be added to allow updating of data.

The OpenAll() method uses the GetRowsetProperties() method of the CTable_1Accessor class to set the properties as a part of processing the open of the database. This can be seen by setting a debugger breakpoint in the method. However other calls to the Open() method do not invoke the GetRowsetProperties() function so these other calls to Open() must have a property set specified in order to modify the default properties for the resulting row set. So not only was a change needed for the GetRowsetProperties() method of the CTable_1Accessor class in the wizard generated include file, there also needed to be the addition of the proper properties for each Open() in order to allow for the use of the SetDate() method to update the row set.

The second problem with the example posted is that the Close() function is not being used to close the retrieved rowset after using any of the various Open() variations. The lack of a Close() is what was causing the assert when the second and later Open() were called.

The modified source is below. This was created by starting a new Windows Console project with ATL in Visual Studio 2005 and then using the Class wizard to create the table access ATL based class. With the wizard I checked the boxes in the wizard dialog to allow for Insert, Delete, and Update which then created the proper GetRowsetProperties() method as described by Roman R. for the Accessor used by the CTable1 class (class CTable_1 : public CCommand<CAccessor<CTable_1Accessor> >).

The modified example program follows.

#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;
}

The output produced by the above program follows. This output shows the original row values followed by the results of several different changes to some of the row values.

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

Outras dicas

You probably open the table without requesting capability to change/update data. Without requesting this, you don't have interfaces used by the SetData call and hence the error. On your table class you should have something like (this should go somewhere into Table_1.h you did not post):

    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));
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top