Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Making ATL OLE DB Provider templates support updating of data - Part 1

0.00/5 (No votes)
20 Feb 2000 1  
The ATL OLE DB Provider templates only seem to support read-only rowsets and making them support updating of data isn't as easy as you'd expect!
  • Download the Simple Data Object with update support - 58 Kb
  • Updating data through an ADO recordset

    First it's worth clearing up some confusion about client and server side cursors and our rowset. Normally selecting either client or server side cursors is a simple choice between network traffic and local storage. Server side cursors are physically located with the data and in the case of most OLE DB providers that's probably on the far end of a network connection to your database server. With our rowset that's used to access our data object our server side is inside our data object... If we could get all of the data bound controls to work with this server side cursor implementation then we wouldn't need to bother with client side cursors at all, but for some reason most Microsoft written data bound controls will fail to fetch any data from these rowsets if the cursor setting is set to server side. Selecting client side cursors causes OLE DB to insert the Client Cursor Engine between your rowset and consumers, this is an OLE DB service component that adds functionality (client side data caching) that you don't supply yourself. The problem with using the CCE with our rowset is that the data is already all on the client side, so the caching just duplicates data and takes up twice the storage that we'd usually require... However, at present, if you need to allow consumers to update your data via Microsoft written data bound controls then you have to use the CCE...

    Supporting updating from our rowset is relatively easy if if we're using server side cursors, however if we select client side cursors then things are more complex. We'll address the server side update issue in this article and cover the changes that are required for using client side cursors in the following article.

    Implementing IRowsetChange

    The OLE DB provider documentation seems to imply that if you want your provider to be updateable then you need to implement either IRowsetChange or IRowsetUpdate. IRowsetChange has all you need for adding new rows, deleting rows and changing data. IRowsetUpdate adds the ability to batch together a series of changes and apply them to the data source in one go. Interestingly the Janus Grid will use IRowsetChange for all updates if IRowsetUpdate is not available, but will use the later if it is available. As  IRowsetUpdate is more complex to implement and doesn't add any value to our examples we won't bother with it. When attempting to get the client cursor engine updates working I added support for IRowsetUpdate but it neither helps nor hinders in getting CCE updates to work...

    IRowsetChange is a relatively simple interface to implement, consisting of three fairly straight-forward methods:

       HRESULT DeleteRows(
          HCHAPTER hChapter, 
          ULONG cRows, 
          const HROW rghRows[], 
          DBROWSTATUS rgRowStatus[]);
    
       HRESULT InsertRow(
          HCHAPTER hChapter, 
          HACCESSOR hAccessor, 
          void *pData, 
          HROW *phRow);
    
       HRESULT SetData(
          HROW hRow, 
          HACCESSOR hAccessor, 
          void *pSrcData);

    A provider can choose to implement any or all of of the three methods above, and return DB_E_NOTSUPPORTED for any functionality that it does not support. It reports its level of support via the DBPROP_UPDATABILITY property. See the interface documentation for more detail. This makes implementing the methods slightly more complex as they must first check to see that the rowset that they're being used on supports the operation required. The other main complication with the IRowsetChange methods is handling the consumer notification stages correctly. Each method can cause multiple notifications to be fired into consumers via the IRowsetNotify connection point interface. What's more, consumers can veto some actions by responding appropriately to the notification call.

    Using our implementation of  IRowsetChange allows us to set the following properties in our rowset's property map.

    PROPERTY_INFO_ENTRY_VALUE(IRowsetChange, VARIANT_TRUE)
    
    PROPERTY_INFO_ENTRY_EX(
       UPDATABILITY, 
       VT_I4, 
       DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, 
       DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_DELETE | DBPROPVAL_UP_INSERT, 
       0)
    
    PROPERTY_INFO_ENTRY_EX(
       OWNINSERT, 
       VT_BOOL, 
       DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, 
       VARIANT_TRUE, 
       0)
    
    PROPERTY_INFO_ENTRY_EX(
       OWNUPDATEDELETE, 
       VT_BOOL, 
       DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, 
       VARIANT_TRUE, 
       0)
     
    PROPERTY_INFO_ENTRY_EX(
       REMOVEDELETED, 
       VT_BOOL, 
       DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, 
       VARIANT_TRUE, 
       0)
     
    PROPERTY_INFO_ENTRY_EX(
       IConnectionPointContainer, 
       VT_BOOL, 
       DBPROPFLAGS_ROWSET | DBPROPFLAGS_READ | DBPROPFLAGS_WRITE, 
       VARIANT_TRUE, 
       0)

    Supporting notifications

    To support consumer callbacks via IRowsetNotify our rowset needs to be a connection point container and we need to support the connection of  IRowsetNotify connection points. This is achieved relatively easily using the ATL connection point support. 

    The actual notification firing code is slightly more complex than the event firing code that you can get ATL to generate for you as the consumer is allowed to respond to some notifications and veto the change occurring in the rowset. This means that we need to pay special attention to the return values of the notification calls and react appropriately to requests to veto changes.

    The IRowsetNotify event source is actually quite simple, but using the events is complex due to the nature of the event types and event phases that are possible. For example, before an update we might send an "column set" "ok to do" event followed by a "column set" "about to do" event, if a consumer vetoes either of these events then all consumers are sent a "column set" "failed to do" event. Because of this complexity we wrap the groups of events in helper methods which can be called from within our IRowsetChange methods.

    The ordering and details of the notification events required by the OLE DB specification is quite difficult to determine from the documentation. The events that this code sends appear to be adequate but your mileage may vary... One method of investigating these events is to watch the sequence of events fired by the client cursor engine when client side cursor updates are applied to a recordset. 

    An updateable proxy rowset

    Now that we have the IRowsetChange and IRowsetNotify event source interfaces we can implement a proxy rowset which uses these to provide update capability to our data object's rowset. CUpdatableProxyRowsetImpl derives from the proxy rowset that we developed over the previous articles and also from our implementations of IRowsetChange and IConnectionPointContainer. 

    template <
       class DataClass,
       class T, 
       class CreatorClass, 
       class Storage = CRowsetStorageProxy<T>, 
       class ArrayType = CRowsetArrayTypeProxy<T, Storage>,
       class RowClass = CSimpleRow,
       class RowsetInterface = IRowsetImpl < T, IRowset, RowClass> >
    class CUpdatableProxyRowsetImpl:
       public CProxyRowsetImpl< 
          DataClass, 
          T, 
          CreatorClass, 
          Storage, 
          ArrayType,
          RowClass,
          RowsetInterface >,
       public IRowsetChangeImpl<T, Storage>,
       public IConnectionPointContainerImpl<CUpdatableProxyRowsetImpl>

    Within our rowset class we handle the connection point container that's required for our rowset notifications. We also provide code that calls our data object's rowset to perform updates. Inserts and deletes are done using the operations already available on our rowset's proxy storage object. 

    Please note that the SetDataHelper() method in CUpdatableProxyRowsetImpl has a horrible hard coded limit of 256 bytes of data per column. This could easily be removed but is left as an exercise for the reader ;)

    Changes to our data object's rowset

    We need to change our data object's rowset object to take advantage of the updateable functionality we have provided. It now inherits from our new updateable proxy rowset and it needs to implement the UpdateColumn() method. Once this is done our object can be updated via ADO as long as have selected server side cursors. If we run the VB test harness program and create a table, then obtain a rowset from the data object with a server side cursor and batch optimistic locking we can click the button to display the rowset in the Janus Grid and set a break point inside the data object rowset's UpdateColumn() method. When we change the data in the grid we end up inside the rowset's UpdateColumn() method and the data is updated.

    Interestingly for the example above to work we don't need to set the data source property DBPROP_DATASOURCEREADONLY to VARIANT_TRUE, but we probably should do... Also, inside our proxy rowset we don't indicate that that the columns are writable by adding DBCOLUMNFLAGS_WRITE to the column flags by default, again we should do. Failure to mark the column data as writable by adding this flag prevents client side cursor updates from working at all... (and yes, it took me ages to find out why they were failing in code that had previously worked fine

    Insufficient base table information...

    Our rowset now supports updates but it still doesn't work with many of the common Microsoft data bound controls. The Data Grid doesn't display data if we use server side cursors (I've no idea why and I have an open support call with Microsoft over it), so to update data from the Microsoft data bound controls we need to support updating via the client cursor engine. However if we select client side cursors and optimistic locking in our test program and try to change data in the Data Grid we get an error message "Insufficient base table information for updating or refreshing". Switching to batch optimistic locking simply causes the error to occur when we issue an UpdateBatch command on the recordset rather than as soon as the data is changed... We address the cause of this error and its solution in the next article

    The source was built using Visual Studio 6.0 SP3. Using the July edition of the Platform SDK. If you don't have the Platform SDK installed then you may find that the compile will fail looking for "msado15.h". You can fix this problem by creating a file of that name that includes "adoint.h". 

    Please send any comments or bug reports to me via email. For any updates to this article, check my site here.

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here