<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Rambling Comments</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/" />
    <link rel="self" type="application/atom+xml" href="http://www.lenholgate.com/blog/atom.xml" />
    <id>tag:www.lenholgate.com,2010-12-10:/blog//12</id>
    <updated>2010-12-22T13:22:12Z</updated>
    
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type Pro 5.12</generator>

<entry>
    <title>OLEDB - Disconnected Recordsets</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/2000/05/oledb---disconnected-recordsets.html" />
    <id>tag:www.socketframework.com,2000:/blog//12.128</id>

    <published>2000-05-21T00:00:01Z</published>
    <updated>2010-12-22T13:22:12Z</updated>

    <summary>If you are going to use the client cursor engine then often it&apos;s a good idea to disconnect your recordset......</summary>
    <author>
        <name>Len</name>
        
    </author>
    
        <category term="OLEDB" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Reprints" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Source Code" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Way back" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="en-us" xml:base="http://www.lenholgate.com/blog/">
        If you are going to use the client cursor engine then often it&apos;s a good idea to disconnect your recordset...
        <![CDATA[<p><b>Disconnected Recordsets</b><br />
The normal procedure for using the Client Cursor Engine is to open your recordset with client side cursors and then disconnect it from the data source. This causes the CCE to pull all of your data out of the data source and effectively marshal it by value to your client. Though the code available so far allows the use of the CCE there's no way to disconnect the recordset from your simple data object. We'll address that issue now.</p>

<p><b>What happens when we disconnect?</b><br />
When you disconnect a recordset by setting the active connection to nothing what happens inside your provider is that the <code>Cancel</code> method is called on your command object. In fact, the <code>Cancel</code> method is always called when the consumer is done with the underlying data source. Thus, we can intercept the call to <code>Cancel</code> and use it as a trigger to release our connection to the data object. To be able to do this we need to be able to communicate between the <code>Command</code> object and the rowset that it creates. To achieve this communication we can add a new interface to our proxy rowset:</p>
<pre class="brush: cpp gutter: false">interface IDisconnectableRowset : IUnknown
{
   HRESULT DisconnectRowset();
};</pre>
<p>We can then query for this interface from our <code>Command</code> object as the last thing we do inside of the <code>Execute</code> method.</p>
<pre class="brush: cpp gutter: false">if (SUCCEEDED(hr))
{
   hr = pGetAsRowset-&gt;GetAsRowset(
      pOurUnknown, 
      pUnkOuter, 
      riid, 
      pcRowsAffected, 
      ppRowset);
           
   pOurUnknown-&gt;Release();
}
  
if (SUCCEEDED(hr))
{
   hr = (*ppRowset)-&gt;QueryInterface(
      IID_IDisconnectableRowset, 
      (void**)&amp;m_pRowset);
}
  
pGetAsRowset-&gt;Release();</pre>
<p>The <code>Command</code> object now has a way to tell the rowset that it has been disconnected, so we can add an implementation of <code>ICommand::Cancel()</code> that does that.</p>
<pre class="brush: cpp gutter: false">HRESULT CConversionProviderCommand::Cancel()
{
   HRESULT hr = ICommandTextImpl<cconversionprovidercommand>::Cancel();
  
   ATLTRACE2(atlTraceDBProvider, 0, "CConversionProviderCommand::Cancel\n");
  
   if (SUCCEEDED(hr))
   {
      if (m_pRowset)
      {
         hr = m_pRowset-&gt;DisconnectRowset();
         m_pRowset-&gt;Release();
         m_pRowset = 0;
      }
   }
  
   return hr;
}</cconversionprovidercommand></pre>
<p>First calling our base class to see if we can be cancelled and if so performing the disconnection on the rowset object.</p>

<p><b>What does the Rowset do?</b><br />
I chose to make all rowsets derived from <code>CProxyRowsetImpl</code> be disconnectable, so that's where the implementation of <code>IDisconnectableRowset::DisconnectRowset()</code> lives. It's fairly simple, it just causes the rowset to release the data object that it's a proxy rowset for. If nobody else has a reference to the data object at that point then it will cease to exist, that seems pretty disconnected to me...</p>
<pre class="brush: cpp gutter: false">template &lt;
   class DataClass, 
   class T, 
   class CreatorClass, 
   class Storage, 
   class ArrayType, 
   class RowClass, 
   class RowsetInterface&gt;
HRESULT CProxyRowsetImpl&lt;
   DataClass, 
   T, 
   CreatorClass, 
   Storage, 
   ArrayType, 
   RowClass, 
   RowsetInterface&gt;::DisconnectRowset()
{
   ObjectLock lock(this);
  
   if (m_pDataObject)
   {
      m_pDataObject = 0;
   }
  
   if (m_pUnkDataObject)
   {
      m_pUnkDataObject-&gt;Release();
      m_pUnkDataObject = 0;
   }
  
   return S_OK;
}</pre>
<p>That's all there is to it. We now have a disconnectable rowset.</p>

<p><b>Download</b><br />
The following source built using Visual Studio 6.0 SP3. Using the July 2000 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".<br />
If your system drive isn't <code>D:\</code> then you'll have to change the #import statements in IGetAsADORecordsetImpl.h and IGetAsADORecordset.cpp.<br />
</p><ul><li><a href="http://www.lenholgate.com/zips/SimpleDataObject-7.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-7.zip']);">Simple Data Object with Disconnected Recordset support</a></li>
<li><a href="http://www.lenholgate.com/zips/C++GridTest.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'C++GridTest.zip']);">C++ client example</a></li>
<li><a href="http://www.janusys.com">Get a trial version of the Janus GridEx that is used in the test harness above</a></li></ul><br />
<b>Revision history</b><br />
<ul><li>21st May 2000 - Initial revision at <a href="http://www.jetbyte.com">www.jetbyte.com</a>.</li>
<li>2nd October 2000 - Fixed some build configuration errors. Thanks to Charles Finley for reporting these. </li>
<li>9th February 2001 - Added the C++ client example.</li>
<li>19th October 2005 - reprinted at <a href="http://www.lenholgate.com">www.lenholgate.com</a>.</li></ul>
<b>Other articles in the series</b><br />
<ul><li><a href="http://www.lenholgate.com/archives/000541.html">Objects via ADO</a> - ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!</li>
<li><a href="http://www.lenholgate.com/archives/000542.html">Custom Rowsets</a> - The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that's not really the case at all!</li>
<li><a href="http://www.lenholgate.com/archives/000543.html">IRowsetLocate and Bookmarks</a> - Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.</li>
<li><a href="http://www.lenholgate.com/archives/000544.html">Updating data through an ADO recordset</a> - 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!</li>
<li><a href="http://www.lenholgate.com/archives/000545.html">Client Cursor Engine updates</a> - Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...</li>
<li><b>Disconnected Recordsets</b> - If you are going to use the client cursor engine then often it's a good idea to disconnect your recordset...</li></ul><p></p>]]>
    </content>
</entry>

<entry>
    <title>OLEDB - Client Cursor Engine updates</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/2000/01/oledb---client-cursor-engine-updates.html" />
    <id>tag:www.socketframework.com,2000:/blog//12.124</id>

    <published>2000-01-02T01:00:02Z</published>
    <updated>2010-12-22T13:23:41Z</updated>

    <summary>Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops......</summary>
    <author>
        <name>Len</name>
        
    </author>
    
        <category term="OLEDB" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Reprints" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Source Code" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Way back" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="en-us" xml:base="http://www.lenholgate.com/blog/">
        Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...
        <![CDATA[<p><b>Client Cursor Engine Updates</b><br />
It turns out that supporting updates through the client cursor engine is relatively easy. Discovering that it's relatively easy was extremely difficult. An article that was recently added to the MSDN gives complete and full information on what a rowset needs to support for updates via the CCE to be possible. Before that article was published you had to rely on guess work to find out how to do it... By the way, if anyone from Microsoft reads this, the client cursor engine article really rocks, we need more articles with that level of technical information.</p>

<p><b>A9645971-91EE-11D1-9251-00C04FBBBFB3</b><br />
I must have spent around 6 months trying to get client cursor updates to work. I tried adding  <code>IRowsetUpdate</code>, an obvious choice, but completely unnecessary. I tried adding all manner of other interfaces, guessing at random and failing completely. The main problem with client cursor updates is that at the point when the dreaded "Insufficient base table information for updating or refreshing" message is issued your OLE DB provider's session object is queried for an undocumented interface. The IID is <code>{A9645971-91EE-11D1-9251-00C04FBBBFB3}</code> and according to a Microsoft support person this interface is the subject of a documentation bug which relates to updating providers that do not support SQL.</p>

<p><b>So, what's the secret</b><br />
It turns out that all you need to do to get rid of the "Insufficient..." error message is to implement <code>IColumnsRowset</code> which isn't too hard as most of the required information can be obtained from a call to <code>IColumnsInfo::GetColumnInfo()</code> which is supported by the ATL templates. The main issues with the implementation of <code>IColumnsRowset</code> are that <code>GetAvailableColumns()</code> must return at least the following three optional meta data columns:</p>
<pre class="brush: cpp gutter: false">DBCOLUMN_BASETABLENAME
DBCOLUMN_BASECOLUMNNAME 
DBCOLUMN_KEYCOLUMN
</pre>
<p>and that <code>GetColumnsRowset()</code> must return a rowset where these optional meta data columns are present and contain valid information.</p>

<p>In our current situation the table name is not really relevant, so we can fill in any old name and the base column names can be the same as the column names that <code>IColumnsInfo</code> reports.</p>

<p>Once this is done we can add support to our updateable proxy and add the appropriate rowset property and the "Insufficient..." error is no more. Unfortunately we have yet to actually fix the problem.</p>

<p>Once a rowset supports <code>IColumnsRowset</code> the client cursor engine has enough information to issue insert, update and delete requests to the rowset in SQL that is written in terms of the 'base table' information. That is it knows the real names of the columns and tables involved in an update and can write an update statement that can deal with rowsets that are the result of joins or data shape commands on an underlying data source. Unfortunately for us this means we are required to support SQL for updates, inserts and deletes.</p>

<p>The resulting SQL arrives at our provider as a command. If you create a table, obtain an ADO recordset from it with client side cursors and optimistic locking then a a breakpoint set in <code>CConversionProviderCommand::Execute()</code> will be hit as soon as you change data in the Data Grid and tab away from the cell. The value of <code>m_strCommandText</code> will be the SQL, something along the lines of:</p>
<pre class="brush: cpp gutter: false">   update table set Col1 = ? where Col1 = ? and<br />
   Col2 = ? and Col3 = ? and Col4 = ?
</pre>
<p>The question marks are place holders for values that have been passed to us in the <code>DBPARAM</code>s accessor. This is because we support <code>ICommandWithParameters</code> (which we need to for our conversion to an ADO recordset). If we didn't support <code>ICommandWithParameters</code> then the SQL would hold the values in the text string itself.</p>
<pre class="brush: cpp gutter: false">   update table set Col1 = 'a' where Col1 = '1'
   and Col2 = '2' and Col3 = '3' and Col4 = '4'
</pre>
<p>The reason for the painful where clause is that our data object doesn't have any key columns. The CCE wont use a bookmark column as a key column so unless your data source has a valid unique key column and the column flags are set to include <code>DBCOLUMNFLAGS_ISROWID</code> inside of your data object's <code>GetColumnInformation</code> method then the where clause will be written so that all columns are used to identify the row that needs to be changed.</p>

<p><b>An exercise for the reader...</b><br />
I'm afraid that's where I'm going to leave this particular problem. Parsing the SQL is relatively straight forward as the statements will only ever be generated by the CCE and should remain in a consistent form. Identifying the rowset that your command is to manipulate can be done by retaining a mapping table within the provider and using the key into the mapping table as the base table name returned from a call to <code>GetColumnsRowset</code> for a particular rowset object. The row to be modified is easily located if your data source has a unique key and more laboriously located if not (it's a pity that the CCE can't be made to use the bookmark column if no other unique key is present...). Once you have your row handles you need to create a copy of the accessor that you're passed into your command in the context of the rowset and then call the methods in <code>IRowsetChange</code> to do the actual work.</p>

<p>I may be tempted to write another article which covers this final piece of work...</p>

<p><b>Download</b><br />
The following source built using Visual Studio 6.0 SP3. Using the July 2000 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".</p>

<p>If your system drive isn't <code>D:\</code> then you'll have to change the #import statements in IGetAsADORecordsetImpl.h and IGetAsADORecordset.cpp.<br />
</p><ul><li><a href="http://www.lenholgate.com/zips/SimpleDataObject-6.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-6.zip']);">Simple Data Object with CCE update support</a></li>
<li><a href="http://www.janusys.com">Get a trial version of the Janus GridEx that is used in the test harness above</a></li></ul>
<b>Revision history</b><br />
<ul><li>2nd January 2000 - Initial revision at <a href="http://www.jetbyte.com">www.jetbyte.com</a>.</li>
<li>4th March 2000 - Updated - Server side cursor updates now work with the DataGrid. Thanks to Francois Leclercq for his helpful information about <code>IRowsetLocate::Hash()</code> and to Prash Shirolkar at Microsoft for his support and the OMNI Provider article. </li>
<li>2nd October 2000 - Fixed some build configuration errors. Thanks to Charles Finley for reporting these. </li>
<li>19th October 2005 - reprinted at <a href="http://www.lenholgate.com">www.lenholgate.com</a>.</li></ul><br />
<b>Other articles in the series</b><br />
<ul><li><a href="http://www.lenholgate.com/archives/000541.html">Objects via ADO</a> - ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!</li>
<li><a href="http://www.lenholgate.com/archives/000542.html">Custom Rowsets</a> - The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that's not really the case at all!</li>
<li><a href="http://www.lenholgate.com/archives/000543.html">IRowsetLocate and Bookmarks</a> - Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.</li>
<li><a href="http://www.lenholgate.com/archives/000544.html">Updating data through an ADO recordset</a> - 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!</li>
<li><b>Client Cursor Engine updates</b> - Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...</li>
<li><a href="http://www.lenholgate.com/archives/000546.html">Disconnected Recordsets</a> - If you are going to use the client cursor engine then often it's a good idea to disconnect your recordset...</li></ul><p></p>]]>
    </content>
</entry>

<entry>
    <title>OLEDB - Updating data through an ADO recordset</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/2000/01/oledb---updating-data-through-an-ado-recordset.html" />
    <id>tag:www.socketframework.com,2000:/blog//12.123</id>

    <published>2000-01-02T01:00:01Z</published>
    <updated>2010-12-22T13:24:04Z</updated>

    <summary>The ATL OLE DB Provider templates only seem to support read-only rowsets, and making them support updating of data isn&apos;t as easy as you&apos;d expect!...</summary>
    <author>
        <name>Len</name>
        
    </author>
    
        <category term="OLEDB" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Reprints" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Source Code" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Way back" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="en-us" xml:base="http://www.lenholgate.com/blog/">
        The ATL OLE DB Provider templates only seem to support read-only rowsets, and making them support updating of data isn&apos;t as easy as you&apos;d expect!
        <![CDATA[<p><b>Cursors everywhere</b><br />
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... 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...</p>

<p>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.</p>

<p><b>Implementing IRowsetChange</b><br />
The OLE DB provider documentation seems to imply that if you want your provider to be updateable then you need to implement either <code>IRowsetChange</code> or <code>IRowsetUpdate</code>. <code>IRowsetChange</code> has all you need for adding new rows, deleting rows and changing data. <code>IRowsetUpdate</code> 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 <code>IRowsetChange</code> for all updates if <code>IRowsetUpdate</code> is not available, but will use the later if it is available. As <code>IRowsetUpdate</code> 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 <code>IRowsetUpdate</code> but it neither helps nor hinders in getting CCE updates to work...</p>

<p><code>IRowsetChange</code> is a relatively simple interface to implement, consisting of three fairly straight-forward methods:</p>
<pre class="brush: cpp gutter: false">   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);</pre>
<p>A provider can choose to implement any or all of of the three methods above, and return <code>DB_E_NOTSUPPORTED</code> for any functionality that it does not support. It reports its level of support via the <code>DBPROP_UPDATABILITY</code> 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 <code>IRowsetChange</code> methods is handling the consumer notification stages correctly. Each method can cause multiple notifications to be fired into consumers via the <code>IRowsetNotify</code> connection point interface. What's more, consumers can veto some actions by responding appropriately to the notification call.</p>

<p>Using our implementation of <code>IRowsetChange</code> allows us to set the following properties in our rowset's property map.</p>
<pre class="brush: cpp gutter: false">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)</pre>
<p><b>Supporting notifications</b><br />
To support consumer callbacks via <code>IRowsetNotify</code> our rowset needs to be a connection point container and we need to support the connection of <code>IRowsetNotify</code> connection points. This is achieved relatively easily using the ATL connection point support.</p>

<p>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.</p>

<p>The <code>IRowsetNotify</code> 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 a "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 <code>IRowsetChange</code> methods.</p>

<p>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.</p>

<p><b>An updateable proxy rowset</b><br />
Now that we have the <code>IRowsetChange</code> and <code>IRowsetNotify</code> event source interfaces we can implement a proxy rowset which uses these to provide update capability to our data object's rowset. <code>CUpdatableProxyRowsetImpl</code> derives from the proxy rowset that we developed over the previous articles and also from our implementations of <code>IRowsetChange</code> and <code>IConnectionPointContainer</code>.</p>
<pre class="brush: cpp gutter: false">template &lt;
   class DataClass,
   class T, 
   class CreatorClass, 
   class Storage = CRowsetStorageProxy&lt;T&gt;, 
   class ArrayType = CRowsetArrayTypeProxy&lt;T, Storage&gt;,
   class RowClass = CSimpleRow,
   class RowsetInterface = IRowsetImpl &lt; T, IRowset, RowClass&gt; &gt;
class CUpdatableProxyRowsetImpl:
   public CProxyRowsetImpl&lt; 
      DataClass, 
      T, 
      CreatorClass, 
      Storage, 
      ArrayType,
      RowClass,
      RowsetInterface &gt;,
   public IRowsetChangeImpl&lt;T, Storage&gt;,
   public IConnectionPointContainerImpl&lt;CUpdatableProxyRowsetImpl&gt;</pre>
<p>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.</p>

<p>Please note that the <code>SetDataHelper()</code> method in <code>CUpdatableProxyRowsetImpl</code> 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 ;)</p>

<p><b>Changes to our data object's rowset</b><br />
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 <code>UpdateColumn()</code> method. Once this is done our object can be updated via ADO as long as you 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 <code>UpdateColumn()</code> method. When we change the data in the grid we end up inside the rowset's <code>UpdateColumn()</code> method and the data is updated.</p>

<p>Interestingly for the example above to work we don't need to set the data source property <code>DBPROP_DATASOURCEREADONLY</code> to <code>VARIANT_TRUE</code>, but we probably should do... Also, inside our proxy rowset we don't indicate that that the columns are writable by adding <code>DBCOLUMNFLAGS_WRITE</code> 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!)</p>

<p><b>Server side updates and the DataGrid</b><br />
The Microsoft DataGrid is a demanding consumer. Whilst the changes detailed above work just fine with the Janus Grid, the DataGrid fails to display any data. The reason for this is that the DataGrid also requires that the following rowset properties are set:</p>
<pre class="brush: cpp gutter: false">PROPERTY_INFO_ENTRY_VALUE(LITERALIDENTITY,VARIANT_TRUE)
PROPERTY_INFO_ENTRY_VALUE(STRONGIDENTITY,VARIANT_TRUE)</pre>
<p>These properties tell the grid that each row in the rowset is represented by a single row handle. That is, if the same row is returned from the rowset the row handle is simply add reffed and returned rather than a new row handle being created. This allows a consumer to easily match rows sent to it in notification calls with rows that it already holds.</p>

<p>Once these two are set the DataGrid expects <code>IRowsetLocate::Hash()</code> to be implemented, though I'm at a loss to understand why.</p>

<p><b>Insufficient base table information...</b><br />
Our rowset now supports server side cursor updates and works with some of the common Microsoft data bound controls. However if we select client side cursors and optimistic locking in our test program and try to change data 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 <code>UpdateBatch</code> 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.</p>

<p><b>Download</b><br />
The following source built using Visual Studio 6.0 SP3. Using the July 2000 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".</p>

<p>If your system drive isn't <code>D:\</code> then you'll have to change the <code>#import</code> statements in IGetAsADORecordsetImpl.h and IGetAsADORecordset.cpp.<br />
</p><ul><li><a href="http://www.lenholgate.com/zips/SimpleDataObject-5.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-5.zip']);">Simple Data Object with update support</a></li>
<li><a href="http://www.janusys.com">Get a trial version of the Janus GridEx that is used in the test harness above</a></li></ul><br />
<b>Revision history</b><br />
<ul><li>2nd January 2000 - Initial revision at <a href="http://www.jetbyte.com">www.jetbyte.com</a>.</li>
<li>4th March 2000 - Updated - Server side cursor updates now work with the DataGrid. Thanks to Francois Leclercq for his helpful information about <code>IRowsetLocate::Hash()</code> and to Prash Shirolkar at Microsoft for his support and the OMNI Provider article.</li>
<li>2nd October 2000 - Fixed some build configuration errors. Thanks to Charles Finley for reporting these. </li>
<li>13th October 2005 - reprinted at <a href="http://www.lenholgate.com">www.lenholgate.com</a>.</li></ul><br />
<b>Other articles in the series</b><br />
<ul><li><a href="http://www.lenholgate.com/archives/000541.html">Objects via ADO</a> - ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!</li>
<li><a href="http://www.lenholgate.com/archives/000542.html">Custom Rowsets</a> - The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that's not really the case at all!</li>
<li><a href="http://www.lenholgate.com/archives/000543.html">IRowsetLocate and Bookmarks</a> - Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.</li>
<li><b>Updating data through an ADO recordset</b> - 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!</li>
<li><a href="http://www.lenholgate.com/archives/000545.html">Client Cursor Engine updates</a> - Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...</li>
<li><a href="http://www.lenholgate.com/archives/000546.html">Disconnected Recordsets</a> - If you are going to use the client cursor engine then often it's a good idea to disconnect your recordset...</li></ul><p></p>]]>
    </content>
</entry>

<entry>
    <title>OLEDB - IRowsetLocate and Bookmarks</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/1999/10/oledb---irowsetlocate-and-bookmarks.html" />
    <id>tag:www.socketframework.com,1999:/blog//12.122</id>

    <published>1999-10-16T00:00:01Z</published>
    <updated>2010-12-22T13:24:22Z</updated>

    <summary>Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls....</summary>
    <author>
        <name>Len</name>
        
    </author>
    
        <category term="OLEDB" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Reprints" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Source Code" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Way back" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="en-us" xml:base="http://www.lenholgate.com/blog/">
        Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.
        <![CDATA[<p><b>IRowsetLocate and bookmarks</b><br />
To support some of the more demanding data bound controls we need to support bookmarks. The proxy rowset that we developed in the <a href="http://www.lenholgate.com/archives/000542.html">last article</a> already has some support for bookmarks built in, but the rowset itself doesn't expose <code>IRowsetLocate</code>, or the bookmark related properties, so the bookmark functionality can't be used by consumers. In this article we'll remedy that by adding support for <code>IRowsetLocate</code>.</p>

<p>A quick search for <code>IRowsetLocate</code> in the MSDN leads us to a rather helpful implementation of the interface which can be found in the "<a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmfc98/html/vchowaddingirowsetlocateinterface.asp">Enhancing the Simple Read-Only provider</a>" section of the OLE DB Provider template documentation. Unfortunately the original implementation that was supplied had a few bugs in it, the latest version is better, but it's still buggy when you have more than 256 rows. We'll fix those bugs and then integrate the interface into the proxy rowset. Once that's done it's easy to build implementations of <code>IRowsetScroll</code> and <code>IRowsetExactScroll</code> to complete our bookmark support.</p>

<p><b>Supporting complex Data Bound controls</b><br />
One of the advantages of OLE DB is that you can choose how much functionality you wish to expose. If your provider is relatively simple then you are not forced to provide all of the complex features that you would expect to find in a provider for a relational database. Although there are various "levels" of "conformance" with the OLE DB specification it's often difficult to work out exactly what functionality is required of your provider. The flexibility of the OLE DB specification is thus something of a double edged sword, whilst it's easy to conform to the spec it's equally easy for consumers to require you to implement some additional functionality before they can use your provider.</p>

<p>This wouldn't be so bad if all data bound controls came with documentation that stated exactly what they expected of a provider. Unfortunately I've yet to find a control that comes with such documentation. Each control has an arbitrary set of requirements that it makes on a provider. It would seem that the only way to be sure that your provider could work will all possible consumers would be to implement the entire OLE DB specification. However even that might not be enough for some of Microsoft's own controls. If you want to support read/write functionality on Microsoft's Data Grid, for example, it appears that you are required to support some interfaces that aren't present in the documentation or SDK!</p>

<p>If OLE DB were to use the standard COM functionality discovery mechanism, <code>QueryInterface()</code>, then it would be relatively easy to work out what certain consumers required of you by simply watching for the interfaces they asked you for. Unfortunately OLE DB uses a property based mechanism for functionality discovery. If you fail to answer the "what properties do you support" questions correctly then you'll never see any QI calls for the interfaces that provide the functionality that your consumers desire. I can understand why the designers did it this way: a consumer may need to discover lots of information about a provider in one go and multiple interface requests and calls would be horribly inefficient, but it makes it very difficult to experiment and find out what third party consumers require of your provider. OLEB Service Providers confuse the matter even more by stepping in and providing extra functionality for you, in certain circumstances, if it can synthesize the functionality that you're lacking from functionality that you present. Finally the ATL implementation of the property mechanism is far from easy to trace through and doesn't have any kind of debug output to show exactly what property calls are occurring.</p>

<p>This makes it particularly difficult to add features to your OLE DB provider as it's impossible to know how many controls you will be able to support without actually testing your code with each of the controls. Even a relatively simple piece of functionality, like bookmarks, can be difficult to implement because of the lack of documentation about what a particular control requires.</p>

<p><b>Determining what a control needs</b><br />
As an example of the difficulties of adding functionality to your provider, take a look at this simple test program and run it with the object that we developed in our last article. The test is a simple Visual Basic program that creates our object and allows us to obtain a recordset from it and then connect the recordset to various data bound controls. As you will see, if you press "Make Table" and then "Get Recordset" and then press the various buttons to connect the recordset to the controls each control reacts differently to our minimalist recordset implementation: Only the MSHFlexGrid works. The Data List and Data Combo remain blank. The Linked Edit works, but then that's a "simple data bound control" - one that's only bound to a single row - so we would expect it to work... The Data Grid tells us that it needs bookmarks and the Janus GrixEx just reports that we're an "invalid recordset".</p>

<p>Contrast this with the results obtained when we check the "Client" check box in the Cursor Location frame. When using the client cursor engine ADO steps in and implements the missing functionality for us. This is great, and if we really want to use client side cursors then our work here is done. However, there's a major problem with client side cursors - they're client side. In this context that means that all of the effort that we went to so that our data object could retain ownership of its data and only convert it on demand  was wasted. The ADO cursor engine simply fetches all of our data from our object into the cursor engine and adds functionality to the rowset implementation... Not ideal.</p>

<p>We can try and work out what functionality is required of us by watching the debug strings output our provider as we attempt to attach it to each control. Add <code>_ATL_DEBUG_QI</code> to the project settings for the provider and do a rebuild all. Then set up Visual Basic as the debug target for the OLE DB provider and start a debug run. Load the test harness project into Visual Basic and run it. You should then see the debug string output displayed in the Visual C++ debugger. Unfortunately the results are somewhat misleading. As I indicated above, whilst we do see some QI calls, most of the negotiation appears to occur through a requests for the rowset's properties (the Janus GridEx doesn't even do that!).</p>

<p>The Data Grid QI's for <code>IConnectionPointContainer</code> (probably seeing if we support change notifications), then  <code>IColumnsInfo</code> and <code>ICommandText</code> and finally makes a get properties call... The DataList just asks for <code>ICommandText</code> and <code>IColumnsRowset</code> before making a get properties call. The DataCombo calls get properties then QIs for <code>IConnectionPointContainer</code>, <code>IColumnsInfo</code> and <code>ICommandText</code>, makes another get properties call and then QIs for <code>IColumnsRowset</code>. None of this would lead us to believe that bookmarks and <code>IRowsetLocate</code> were the feature that was lacking...</p>

<p>Only the Microsoft Data Grid has given us any meaningful information about what it requires of us and that was via an error message! From that we can look up bookmark support in the OLE DB documentation and discover that we must implement <code>IRowsetLocate</code> and answer correctly for several property values... As we'll see, implementing bookmarks on our rowset will make it work with some of the controls in the test harness program, the others will at least give us more hints at what else they require. It's unfortunate that we could only have discovered this fact by trial and error.</p>

<p><b>Supporting IRowsetLocateImpl</b><br />
To add support for Bookmarks we need to change our DataObjectRowset object so that it derives from a <code>CProxyRowsetImpl</code> that itself has <code>IRowsetLocate</code>, rather than <code>IRowset</code>, as its base class. As mentioned before, we can leverage (steal) an implementation from the "<a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmfc98/html/vchowaddingirowsetlocateinterface.asp">Enhancing the Simple Read-Only provider</a>" section of the OLE DB Provider template documentation.</p>

<p>The resulting changes to our DataObjectRowset look something like this:</p>
<pre class="brush: cpp gutter: false">// This is what we did have...
  
class CDataObjectRowset : 
   public CProxyRowsetImpl&lt;
      CMyDataObject, 
      CDataObjectRowset, 
      CConversionProviderCommand&gt;
{
  
// This is what we have now...
  
class CDataObjectRowset : 
   public CProxyRowsetImpl&lt;
      CMyDataObject, 
      CDataObjectRowset, 
      CConversionProviderCommand,
      CRowsetStorageProxy&lt;CDataObjectRowset&gt;, 
      CRowsetArrayTypeProxy&lt;
         CDataObjectRowset, 
         CRowsetStorageProxy&lt;CDataObjectRowset&gt; &gt;,
      CSimpleRow,
      IRowsetLocateImpl &lt; CDataObjectRowset &gt; &gt;
{</pre>
<p>It's times like this that you begin to wish that you'd chosen a different order for the default template parameters! The storage and array proxy classes and the simple row object were all defaulted in our original implementation. Now, as we need to replace the final template parameter, we must copy the default values from the <code>CProxyRowsetImpl</code> template and just replace the base class parameter.</p>

<p>So, our rowset now derives from our implementation of <code>IRowsetLocate</code>, we now need to hook it up to our interface map. Since all of the interfaces are currently handled by the <code>CProxyRowsetImpl</code> class we need to add a com map to our <code>CDataObjectRowset</code> that chains to the one that's present in the <code>CProxyRowsetImpl</code> and then add support for <code>IRowsetLocate</code>. Much like this:</p>
<pre class="brush: cpp gutter: false">BEGIN_COM_MAP(CDataObjectRowset)
   COM_INTERFACE_ENTRY(IRowsetLocate)
   COM_INTERFACE_ENTRY_CHAIN(ProxyRowsetClass)
END_COM_MAP()
</pre>
<p>To make the chaining easier we've added a typedef to the <code>CProxyRowsetImpl</code> so that derived classes can use "<code>ProxyRowsetClass</code>" rather than having to specify the template and all the template parameters again!</p>

<p>This is all well and good, but unless we tell our consumers that we support bookmarks via the correct OLE DB properties they'll never request the <code>IRowsetLocate</code> interface.</p>

<p><b>Adjusting the property map</b><br />
At first sight, you could be confused into thinking that the default rowset properties that ATL supplies you with includes support for bookmarks. After all, the rowset's property map looks something like this:</p>
<pre class="brush: cpp gutter: false">BEGIN_PROPSET_MAP(CConversionProviderCommand)
   BEGIN_PROPERTY_SET(DBPROPSET_ROWSET)
      PROPERTY_INFO_ENTRY(IAccessor)
      PROPERTY_INFO_ENTRY(IColumnsInfo)
      PROPERTY_INFO_ENTRY(IConvertType)
      PROPERTY_INFO_ENTRY(IRowset)
      PROPERTY_INFO_ENTRY(IRowsetIdentity)
      PROPERTY_INFO_ENTRY(IRowsetInfo)
      PROPERTY_INFO_ENTRY(IRowsetLocate)
      PROPERTY_INFO_ENTRY(BOOKMARKS)
      PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED)
      PROPERTY_INFO_ENTRY(BOOKMARKTYPE)
      PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS)
      PROPERTY_INFO_ENTRY(CANHOLDROWS)
      PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS)
      PROPERTY_INFO_ENTRY(LITERALBOOKMARKS)
      PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS)
   END_PROPERTY_SET(DBPROPSET_ROWSET)
END_PROPSET_MAP()
</pre>
<p>All is not what it seems. If you look in atldb.h you'll see that the property info entry macro expands to include some "default" flags, types and values that are specific for each property supplied. These are used to fill in the property map. What can be confusing is that specifying a property info entry for <code>IRowset</code> results in flags that say you DO support the interface whereas specifying a property info entry for <code>IRowsetLocate</code> results in flags that say you DONT support the interface! This is hardly intuitive. To determine which properties you support from the property map you must search through atldb.h and cross reference the other macros that it contains. I feel it would be far better if all of these <code>PROPERTY_INFO_ENTRY</code> macros were in fact <code>PROPERTY_INFO_ENTRY_VALUE</code> macros (these force you to specify the value of the property, you can then see, at a glance, if you do or do not support a property etc.) of course I understand why it's done this way, it makes the wizard easier to code...</p>

<p>So, what the default property map actually says is this:</p>

<p>We do support the following interfaces: <code>IAccessor</code>, <code>IColumnsInfo</code>, <code>IConvertType</code>, <code>IRowset</code>, <code>IRowsetIdentity</code>, <code>IRowsetInfo</code>. But we don't support <code>IRowsetLocate</code>.</p>

<p>We answer <code>true</code> for these properties <code>CanFetchBackwards</code>, <code>CanHoldRows</code> and <code>CanScrollBackwards</code>. But <code>false</code> for these <code>Bookmarks</code>, <code>BookmarkSkipped</code>, <code>BookmarkType</code>, <code>LiteralBookmarks</code> and <code>OrderedBookmarks</code>.</p>

<p>Obvious, isn't it.</p>

<p>So, it should just be a case of us using the <code>PROPERTY_INFO_ENTRY_VALUE</code> macro and specifying the correct values, such as <code>VARIANT_TRUE</code> for our <code>IRowsetLocate</code> property? It would be nice if it were this easy. Unfortunately the flags specified if we do that means that the property is read only. OLE DB properties are used by both the provider to indicate the functionality that it supports and by the consumer to indicate the functionality it requires. If we were to use the <code>PROPERTY_INFO_ENTRY_VALUE</code> macro we would end up forcing the consumer to accept that we must provide <code>IRowsetLocate</code>. It's better for us to make the property read/write so that the consumer can read the property and see that we support the interface and then write to the property and set it to <code>false</code> if it doesn't require us to support it. This may allow us to optimise some memory usage as we will know that we will never be asked for the interface...</p>

<p>So, to specify our bookmark properties we need to use the mother of all property map macros; <code>PROPERTY_INFO_ENTRY_EX</code>... See the code for the resulting map entries.</p>

<p><b>The problems with IRowsetLocateImpl</b><br />
Now that we have all the code in place for our implementation of <code>IRowsetLocate</code> to be requested, we just have to fix a couple of bugs in the code that we're stealing.</p>

<p>The current version of <code>IRowsetLocateImpl</code> that can be found here suffers from one or two problems. Firstly, the bookmarks are declared as being <code>DWORD</code>s yet inside <code>IRowsetLocate</code> they are treated as <code>BYTE</code>s this leads to the implementation failing if a rowset has more than 256 rows. We fix this problem by casting the bookmark pointer to a <code>DWORD</code> pointer before dereferencing them. Secondly there are some rather dubious locking practices employed which can cause the object to be locked and then left in a locked state if an error occurs.</p>

<p>An older version of this implementation (from a previous version of the same sample) additionally had a off by one error on the case where the bookmark requested was  <code>DB_BMKLST</code>.</p>

<p>See the code sample for the fixed implementation.</p>

<p><b>Integration with the proxy rowset </b><br />
Our bookmark implementation is almost complete. The proxy rowset class already provided some support for bookmarks so we don't need to change anything. The support is included in the following places:</p>
<ul><li><code>CProxyRowsetImpl&lt;&gt;::OnPropertyChanged()</code> handles various rowset properties being set, and sets associated properties as required.</li>
<li><code>CProxyRowsetImpl&lt;&gt;::BookmarksRequired()</code> is a helper function that can be called to determine if we need to return a rowset that contains a column with the bookmarks in.</li>
<li><code>CProxyRowsetImpl&lt;&gt;::InternalGetColumnData()</code> handles and populates requests for data from the bookmark column.</li>
<li><code>CProxyRowsetImpl&lt;&gt;:StorageProxy_GetColumnInfo()</code> handles adjusting the column information that we return to optionally include the bookmark column if required.</li></ul>
<p>and finally:</p>
<ul><li><code>CProxyRowsetImpl&lt;&gt;:BuildColumnInfo()</code> always builds a column map that includes the bookmark column, it then allows <code>StorageProxy_GetColumnInfo</code> to decide if we return this column to the caller.</li></ul>
<p><b>Additional bookmark interfaces </b><br />
Whilst <code>IRowsetLocate</code> is the main interface that supports bookmarks there are several others, most notably <code>IRowsetScroll</code> and then short lived <code>IRowsetExactScroll</code>.</p>

<p><code>IRowsetScroll</code> allows for consumers to obtain rows located at approximate positions within a rowset, it can be used when exact positioning is not required. It is derived from <code>IRowsetLocate</code> and an implementation can be found in IRowsetScrollImpl.h.</p>

<p><code>IRowsetExactScroll</code> appears to have been introduced in OLE DB version 2.0 and then became deprecated in OLE DB version 2.1 - though controls often still ask for it. To include support for <code>IRowsetExactScroll</code> you have to be using version 2.x of the Data Access SDK. If you are using version 2.1 or later then you have to include a "deprecated" define to get the interface brought in - it's unfortunate that the define used is not named something more OLE DB specific... <code>IRowsetExactScroll</code> is derived from <code>IRowsetScroll</code>. Because of how we've implemented <code>IRowsetScroll::GetApproximatePosition()</code> - it gets rows at an exact position as our bookmarks are also row numbers - it's trivial to implement <code>IRowsetExactScroll</code> as it can simply call through to <code>IRowsetScroll</code>. Our implementation of <code>IRowsetExactScroll</code> can be found, not too surprisingly, in IRowsetExactScrollImpl.h.</p>

<p>The sample code assumes we're using the MDAC SDK v2.1 or later, so includes the <code>#DEFINE deprecated</code>. Check the OleDb.H file and search for <code>OLEDBVER</code> to get some idea of what version you're using...</p>

<p>As we're using OLE DB v2.1 we may as well have our provider advertise the fact. To do this we need to make a change to the DataSource object's property map. Locate the <code>PROPERTY_INFO_ENTRY</code> macro for the <code>PROVIDEROLEDBVER</code> property and replace it with a <code>PROPERTY_INFO_ENTRY_VALUE</code> macro for that property and specify a value of "2.1".</p>

<p>We can now add support for <code>IRowsetScroll</code> and <code>IRowsetExactScroll</code> to our rowset's property map. <code>IRowsetScroll</code> is easy, just use a <code>PROPERTY_INFO_ENTRY_EX</code> macro. <code>IRowsetExactScroll</code> is slightly more complex as there's no support for this property built in to the ATL wizard and headers - this isnt too much of a problem, we can use the macro as normal, but we have to add an entry to the string table that ATL provided us with. All of the properties that ATL supports have entries in a string table that's added to your project by the wizard. We need to add an entry for <code>IDS_DBPROP_IRowsetExactScroll</code> to this string table for the macro to work.</p>

<p>In the sample we add support for <code>IRowsetExactScroll</code> to the <code>DataObjectRowset</code> object by replacing the <code>IRowsetLocateImpl&lt;&gt;</code> that we used above with <code>IRowsetExactScrollImpl&lt;&gt;</code> and adding corresponding entries to the COM map.</p>

<p>Even with <code>IRowsetExactScroll</code> support the Data Grid, Data List and Data Combo fail to display data. The Data List and Data Combo query for, and use, <code>IRowsetExactScroll</code> and all of them create an accessor, but none of them actually pull any data! This is disappointing to say the least. Especially since the controls now silently fail and give us no obvious indication of what it is we need to do to get them to work...</p>

<p>Still, at least we have the Janus grid working correctly. Chances are that other non Microsoft controls might work with us also!</p>

<p><b>Download</b><br />
The following source 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".</p>

<p>If your system drive isn't <code>D:\</code> then you'll have to change the <code>#import</code> statements in IGetAsADORecordsetImpl.h and IGetAsADORecordset.cpp.</p>
<ul><li><a href="http://www.lenholgate.com/zips/vbtest.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'vbtest.zip']);">Visual Basic Provider test program</a></li>
<li><a href="http://www.lenholgate.com/zips/SimpleDataObject-4.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-4.zip']);">Simple Data Object with bookmark support</a></li>
<li><a href="http://www.janusys.com">Get a trial version of the Janus GridEx that is used in the test harness above</a></li></ul>

<p><b>Revision history</b><br />
</p><ul><li>16th October 1999 - Initial revision at <a href="http://www.jetbyte.com">www.jetbyte.com</a>.</li>
<li>25th October 1999 - Changed the zip files so that they include the correct version of the VB test program...</li>
<li>22nd July 2000 - Bug fix, IGetAsADORecordsetImpl.h, line 77, <code>_Recordset15</code> should be <code>Recordset15</code>. Thanks to Nie Jiantao for reporting this. </li>
<li>2nd October 2000 - Fixed some build configuration errors. Thanks to Charles Finley for reporting these. </li>
<li>12th October 2005 - reprinted at <a href="http://www.lenholgate.com">www.lenholgate.com</a>.</li></ul><br />
<b>Other articles in the series</b><br />
<ul><li><a href="http://www.lenholgate.com/archives/000541.html">Objects via ADO</a> - ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!</li>
<li><a href="http://www.lenholgate.com/archives/000542.html">Custom Rowsets</a> - The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that's not really the case at all!</li>
<li><b>IRowsetLocate and Bookmarks</b> - Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.</li>
<li><a href="http://www.lenholgate.com/archives/000544.html">Updating data through an ADO recordset</a> - 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!</li>
<li><a href="http://www.lenholgate.com/archives/000545.html">Client Cursor Engine updates</a> - Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...</li>
<li><a href="http://www.lenholgate.com/archives/000546.html">Disconnected Recordsets</a> - If you are going to use the client cursor engine then often it's a good idea to disconnect your recordset...</li></ul>]]>
    </content>
</entry>

<entry>
    <title>OLEDB - Custom Rowsets</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/1999/09/oledb---custom-rowsets.html" />
    <id>tag:www.socketframework.com,1999:/blog//12.121</id>

    <published>1999-09-15T00:01:01Z</published>
    <updated>2010-12-22T13:24:43Z</updated>

    <summary>The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that&apos;s not really the case at all!...</summary>
    <author>
        <name>Len</name>
        
    </author>
    
        <category term="OLEDB" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Reprints" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Source Code" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Way back" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="en-us" xml:base="http://www.lenholgate.com/blog/">
        The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that&apos;s not really the case at all!
        <![CDATA[<p><b>Implementing a custom OLE DB rowset</b><br />
This article continues from where we left off in the <a href="http://www.lenholgate.com/archives/000541.html">previous article</a>. We have all of the framework in place to provide ADO recordset interfaces on our simple data object. All we need to do now is replace the wizard-generated OLE DB rowset object with one that allows us to access our object's data.</p>

<p>The rowset object that the ATL wizard has provided us with is of no use to us. It's fine for simple data where you can copy the data to be made available via ADO into the rowset's array. We want to retain control of our data and not be forced to create a copy of it, so the rowset needs to access our data in-place in our data object.</p>

<p>At first it may seem that the design of the ATL OLE DB provider templates is completely inappropriate for our needs. However, the design is actually very flexible and we can replace two simple components and provide the functionality that we require.</p>

<p><b>The "proxy" rowset</b><br />
The standard OLE DB template rowset object, <code>CRowsetImpl</code>, provides access to data that's stored in a contiguous array within the rowset object itself. This is fine for simple example programs but almost useless for our required application. We want to keep our data stored within our simple data object, it may be too costly to copy all of the data into a new rowset object just to provide access via ADO. We'd rather leave the data where it is and just provide access to it.</p>

<p>Luckily the <code>CRowsetImpl</code> template relies on two template parameter classes for storing its data. The template itself looks something like this:</p>
<pre class="brush: cpp gutter: false">template &lt;
   class T, 
   class Storage, 
   class CreatorClass,
   class ArrayType = CSimpleArray&lt;Storage&gt;,
   class RowClass = CSimpleRow,
   class RowsetInterface = IRowsetImpl &lt; T, IRowset, RowClass&gt; &gt;
class CRowsetImpl : etc...</pre>
<p>The pieces we're interested in replacing are the <code>Storage</code> and <code>ArrayType</code> classes. These are used by the default implementation to retrieve the rowset data from. By replacing these two template parameters with classes that implement the required functionality we can dictate where the rowset gets its data from.</p>

<p>We'll write a proxy rowset template. It's a proxy because it just takes the place of a rowset object and forwards all of the interesting calls to our data object. The data object stores its data in exactly the same way as before and the proxy rowset accesses the data in-place, on demand. There's no initial startup overhead involved, though there's probably a little overhead in the actual data access.</p>

<p>The proxy rowset template looks like this:</p>
<pre class="brush: cpp gutter: false">template &lt;
   class DataClass,
   class T, 
   class CreatorClass, 
   class Storage = CRowsetStorageProxy&lt;T&gt;, 
   class ArrayType = CRowsetArrayTypeProxy&lt;T, Storage&gt;,
   class RowClass = CSimpleRow,
   class RowsetInterface = IRowsetImpl &lt; T, IRowset, RowClass&gt; &gt;
class CProxyRowsetImpl:
   public CRowsetImpl&lt;
      T,
      Storage,
      CreatorClass,
      ArrayType,
      RowClass,
      RowsetInterface &gt;
</pre>
<p>The important things to note are the classes that are defaulted for the <code>Storage</code> and <code>ArrayType</code> template parameters. These two proxy classes simply forward all requests to the proxy rowset rather than fielding them themselves. The proxy rowset can then pass requests on to the data object that it's associated with. By requiring the data object to provide function bodies for several simple data access and information functions the proxy rowset can deal with all of the technicalities of being an OLE DB rowset but still pass the requests for data and information through to the data object itself.</p>

<p>The functions that are data object specific, and must be provided by the class derived from our proxy rowset are as follows:</p>
<pre class="brush: cpp gutter: false">virtual void GetColumnInformation(
   size_t column, 
   DBTYPE &amp;dbType, 
   ULONG &amp;size, 
   std::string &amp;columnName, 
   DWORD &amp;flags);
</pre>
<p>Called when building the column information structure for the rowset. Column numbering starts at 0. The type size and flags fields should be filled in with the values that are appropriate for this column of data - see  <code>DBCOLUMNINFO</code> for more details. The data type specified in <code>dbType</code> should be the native data type that you are storing your data as. The proxy rowset will handle all data conversion requirements to and from this data type for you.</p>
<pre class="brush: cpp gutter: false">virtual void GetColumnData(
   size_t row,
   size_t column,
   DBTYPE &amp;dbType, 
   ULONG &amp;size, 
   void *&amp;pData,
   bool &amp;bIsUpdatable);
</pre>
<p>Called when access to the data in a particular row and column is required. Row and column numbering starts at 0. Type and size are the actual types and sizes of this element of the data (these are likely to be the same as those returned from <code>GetColumnInformation</code> for the column as a whole except in the case of variable length string data when the size returned here can be the actual length of the string, rather than the maximum length that's returned from <code>GetColumnInformation</code>. The <code>void</code> pointer, <code>pData</code>, should be set to point at the start of the data item itself. The proxy rowset will use this pointer to access the data. The pointer should be set to point straight at your data, you shouldn't allocate a buffer or do any copying. The <code>bIsUpdatable</code> flag isn't relavant until we add functionality to the proxy rowset in a later article to make it support read/write rowsets rather than simple read only rowsets.</p>
<pre class="brush: cpp gutter: false">virtual size_t GetNumColumns();
virtual size_t GetNumRows() const;
virtual HRESULT AddRow();
virtual HRESULT RemoveRow(int nIndex);
</pre>
<p>The others are all fairly obvious.</p>

<p>The proxy rowset contains two pointers that refer to the object that it is representing. These are set up automatically when the rowset is connected to the object. You can access these from within your derived class to provide access to your data object. One pointer is a pointer to your data object's <code>IUnknown</code>, you're unlikely to need to use this, it's only really for maintaining a reference on your object whilst the proxy rowset object is connected to it. The second pointer is a pointer to your object itself. Through this your proxy rowset derived class can get direct access to your data object's internals. It's allowed to do this because we take great care when creating the proxy rowset to make sure it's created in the same COM apartment as the data object.</p>

<p><b>Connecting the rowset to your object</b><br />
Now that we have a rowset object and all of the ADO and OLE DB framework in place all that's left is to create the rowset and connect it to your data object. </p>

<p>We need to move back inside our OLE DB provider objects and intercept the rowset creation request at the command object's execute method. This is where the action will happen...</p>

<p>At present the method probably looks something like this:</p>
<pre class="brush: cpp gutter: false">CSimpleDataObjectRowset *pRowset;
return CreateRowset(
   pUnkOuter, 
   riid,
   pParams, 
   pcRowsAffected, 
   ppRowset, 
   pRowset);
</pre>The ATL OLE DB template function <code>CreateRowset</code> is being called which does the work of building a standard rowset of the type specified and then calls the rowset's execute method to populate it. We don't want any of this to happen, so we can rip out the above and replace it with some code that works with the custom command object we created in the ADO layer. This command has passed us the <code>IUnknown</code> pointer of the data object that we're providing a rowset onto. First we need to get hold of this <code>IUnknown</code> pointer using the parameter accessor that we are passed, then we can get to work...<p></p>

<p>As mentioned above we need to take care to create the rowset object in the same COM apartment as the data object itself. This will allow us to access the data object directly from the rowset object using a normal C++ pointer to the data object. The easiest way of making all of this work is for us to ask the data object itself to create and return the rowset object. Because the data object creates the rowset object as a C++ object we know that the rowset is in the same COM apartment as the data object. Of course, this means we need to get from the OLE DB provider's command object back into the data object.</p>

<p>The easiest way into the data object from the command object is via a COM call. In the command object we <code>QueryInterface</code> on the data object's <code>IUnknown</code> pointer for the <code>_IGetAsOLEDBRowset</code> interface. We then call the interface's only method, <code>GetAsRowset()</code> and pass our own <code>IUnknown</code> pointer in, along with a bunch of other stuff.</p>

<p>The work continues back inside the data object as we execute the <code>GetAsRowset()</code> method of the  <code>_IGetAsOLEDBRowset</code> interface... The template implementation of this method simply calls into the data object on a method called <code>AsRowset()</code> and this is where the work of actually creating the rowset and linking it to the data object actually occurs.</p>

<p>We've jumped through a lot of hoops to get to this point, but all of the code up to now has been generic template implementations which do the right thing. We're now inside a method on our data object and we have derived a class from the proxy rowset implementation to act as our rowset class. With a little custom code like the following, we can create our rowset and link it to our data.</p>
<pre class="brush: cpp gutter: false">HRESULT CMyDataObject::AsRowset(
   /* [in] */ IUnknown *pUnkCreator,
   /* [in] */ IUnknown *pUnkOuter,	
   /* [in] */ REFIID riid,				
   /* [out] */ LONG *pcRowsAffected,			  
   /* [out, iid_is(riid)] */ IUnknown **ppRowset)
{
   CDataObjectRowset *pRowset = 0;
   
   HRESULT hr = CreateRowset(
       pUnkCreator, 
       pUnkOuter, 
       riid, 
       ppRowset, 
       pRowset);
  
   if (SUCCEEDED(hr))
   {
      IUnknown *pUnknown = 0;
     	
      hr = QueryInterface(IID_IUnknown, (void**)&amp;pUnknown);
  
      if (SUCCEEDED(hr))
      {
         hr = pRowset-&gt;LinkToObject(this, pUnknown, pcRowsAffected);    
  
         pUnknown-&gt;Release();
      }
   }
   return hr;
}
</pre>
<p>We can then hand the rowset back to the template implementations and they will take care of returning the object to the ADO layer which will wrap it in an ADO recordset and return it to our Visual Basic client code. We can even defer the most complex part of the code, that of creating and wiring up the rowset object, back to the <code>_IGetAsOLEDBRowset</code> implementation template, the call to <code>CreateRowset()</code> is a template member function which is paramaterised on the rowset class we pass into it. It handles creating the rowset COM object and copying the command object's properties into it - in the same way that the standard ATL OLE DB rowset is created.</p>

<p>The complete working example code for the above can be downloaded from <a href="http://www.lenholgate.com/zips/SimpleDataObject-3.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-3.zip']);">here</a>.</p>

<p><b>So, how do I give ADO access to my object?</b><br />
</p><ul><li>Include IGetAsADORecordset.idl and IGetAsOLEDBRowset.idl in your object's IDL file, and have your object support the <code>IGetAsADORecordset</code>, <code>_Recordset</code> and <code>IGetAsOLEDBRowset</code> interfaces.</li>
<li>Add the <code>IGetAsADORecordsetImpl</code> and <code>IGetAsOLEDBRowsetImpl</code> implementation templates to your class's inheritance hierarchy in your header file.</li>
<li>Add the following macros to your class's COM Map: <br /><code>COM_INTERFACE_ENTRY(_IGetAsOLEDBRowset)</code><br /><code>COM_INTERFACE_ENTRY_CHAIN(IGetAsADORecordsetImpl&lt;CMyDataObject&gt;)</code></li>
<li>Derive a class from <code>CProxyRowsetImpl</code> and provide function bodies for the virtual functions required. These should use the pointer to your data object that's available to them as a protected member variable inherited from <code>CProxyRowsetImpl</code> to access your object's data.</li>
<li>Implement <code>AsRowset()</code>, probably just as shown above.</li>
</ul><p></p>

<p>All of this assumes that you have an OLE DB conversion provider that will do the conversion for you. If you are only doing this for one object, package the conversion provider inside the same DLL as the object, if you're doing lots of objects like this, create a separate provider dll and have all your objects use the one provider.</p>

<p><b>Download</b><br />
The following source built using Visual Studio 6.0 SP3. Using the July 2000 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".</p>

<p>If your system drive isn't <code>D:\</code> then you'll have to change the <code>#import</code> statements in IGetAsADORecordsetImpl.h and IGetAsADORecordset.cpp. <br />
</p><ul><li><a href="http://www.lenholgate.com/zips/SimpleDataObject-3.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-3.zip']);">Simple Data Object with full ADO support</a></li><br />
</ul><p></p>

<p><b>Revision history</b><br />
</p><ul><li>15th September 1999 - Initial revision <a href="http://www.jetbyte.com">www.jetbyte.com</a>.</li>
<li>17th September 1999 - Hacked in a simple solution to a stupid cyclical reference counting problem. </li>
<li>16th October 1999 - Removed the 1st release zip file from the second release zip file (all the code was in there twice!) </li>
<li>22nd July 2000 - Bug fix, IGetAsADORecordsetImpl.h, line 77, <code>_Recordset15</code> should be <code>Recordset15</code>. Thanks to Nie Jiantao for reporting this. </li>
<li>2nd October 2000 - Fixed some build configuration errors. Thanks to Charles Finley for reporting these. </li>
<li>12th October 2005 - reprinted at <a href="http://www.lenholgate.com">www.lenholgate.com</a>.</li></ul><br />
<b>Other articles in the series</b><br />
<ul><li><a href="http://www.lenholgate.com/archives/000541.html">Objects via ADO</a> - ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!</li>
<li><b>Custom Rowsets</b> - The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that's not really the case at all!</li>
<li><a href="http://www.lenholgate.com/archives/000543.html">IRowsetLocate and Bookmarks</a> - Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.</li>
<li><a href="http://www.lenholgate.com/archives/000544.html">Updating data through an ADO recordset</a> - 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!</li>
<li><a href="http://www.lenholgate.com/archives/000545.html">Client Cursor Engine updates</a> - Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...</li>
<li><a href="http://www.lenholgate.com/archives/000546.html">Disconnected Recordsets</a> - If you are going to use the client cursor engine then often it's a good idea to disconnect your recordset...</li></ul><p></p>]]>
    </content>
</entry>

<entry>
    <title>OLEDB - Objects via ADO</title>
    <link rel="alternate" type="text/html" href="http://www.lenholgate.com/blog/1999/09/oledb---objects-via-ado.html" />
    <id>tag:www.socketframework.com,1999:/blog//12.120</id>

    <published>1999-09-15T00:00:01Z</published>
    <updated>2010-12-22T13:25:09Z</updated>

    <summary>ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!...</summary>
    <author>
        <name>Len</name>
        
    </author>
    
        <category term="OLEDB" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Reprints" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Source Code" scheme="http://www.sixapart.com/ns/types#category" />
    
        <category term="Way back" scheme="http://www.sixapart.com/ns/types#category" />
    
    
    <content type="html" xml:lang="en-us" xml:base="http://www.lenholgate.com/blog/">
        ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!
        <![CDATA[<p><b>The problem</b><br />
If you already have a COM object that manages data that is naturally tabular, or, if you have a COM object that has data which is often displayed in a tabular form then it would seem sensible to leverage the work being put into ADO by third party data control manufacturers. Why craft a custom control to display your data when you could use any number of ADO aware controls, if only your COM object could provide an ADO view of itself. Also, by presenting a standard interface to your data object it becomes easy for others to use your object, and you don't have to write the documentation for the interface because it's ADO!</p>

<p>It's quite common to find a legacy data object that would benefit from being accessed via ADO. The problem is that providing ADO access is a non-trivial thing to do. The ATL OLE DB Provider templates are useful for simple situations but appear to fall down when you want your own object to own the data rather than simply copying it all into an array inside the rowset object. Also, getting access to your data isn't that easy, you have to hook yourself up to a data provider, then get a rowset from it, etc.</p>

<p>However, it is possible to extend the ATL templates to allow your object to retain ownership of its data, and it's also possible to hide all of the hoops that you have to jump through to wire up your object to its ADO recordset view.</p>

<p>This article will first cover the mechanics of providing an ADO interface onto a simple data object and then deal with writing a reusable OLE DB rowset implementation that can provide in-place access to the simple data object's data but still be used in an ATL OLE DB provider framework.</p>

<p><b>A simple data object</b><br />
Suppose we have a simple data object that implements the following interface and internally represents its data in a tabular form.</p>
<pre class="brush: cpp gutter: false">interface IMyDataObject : IUnknown
{
   HRESULT SetColumnSize([in] long columnSize);
  
   HRESULT AddColumn(
      [in] BSTR columnName,
      [out, retval] long *index);
  		
   HRESULT GetColumnName(
      [in] long index, 
      [out, retval] BSTR *columnName);
  
   HRESULT AddRow([out, retval] long *index);
  		
   HRESULT RemoveRow([in] long index);
  		
   HRESULT Depth([out, retval] long *depth);
  
   HRESULT Width([out, retval] long *width);
  
   HRESULT SetAt(
      [in] long rowIndex, 
      [in] long columnIndex, 
      [in] BSTR value);
  
   HRESULT GetAt(
      [in] long rowIndex, 
      [in] long columnIndex, 
      [out, retval] BSTR *value);
};</pre>
<p>The object can be used to store a table of strings. It's easy enough to use this object from Visual Basic but the interface isn't ideal. You can download the simple data object and a Visual Basic test program from <a href="http://www.lenholgate.com/zips/SimpleDataObject.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject.zip']);">here</a>.</p>

<p><b>Obtaining an ADO recordset</b><br />
The ADO interface that we will use will access our data object via a custom OLE DB provider. We can add a provider to the simple data object's DLL by selecting Insert New ATL Object and choosing a Provider from the Data Access section of the object wizard. Once we've done this we can add an interface to the simple data object that will allow it to return an ADO recordset view of itself. This interface will use ADO to access the OLE DB provider that we've added and construct a recordset to return to our Visual Basic client code.</p>

<p>The interface that we'll add will look like this:</p>
<pre class="brush: cpp gutter: false">interface IGetAsADORecordset : IDispatch
{
   [id(1), helpstring("method GetAsRecordset")] 
   HRESULT GetAsRecordset(
      [in] CursorLocationEnum CursorLocation,
      [in] LockTypeEnum LockType,
      [in] CursorTypeEnum CursorType,
      [out, retval] VARIANT *pvRecordset);
};</pre>
<p>We've chosen a variant to return the recordset as it means we don't have to worry about using <code>importlib</code> to pull in the ADO type library. It results in one extra QI call for Visual Basic to get the correct interface from the <code>IDispatch</code> pointer stored in the variant but it frees us from a run-time binding to the location of the ADO type library (see knowledge base article Q186387).</p>

<p>The Visual Basic client code can then do something like the following:</p>
<pre class="brush: cpp gutter: false">   Dim dataObject as New MyDataObject
   Dim asRs as IGetAsADORecordset
   Set asRs = dataObject
   Dim rs as ADODB.Recordset
   Set rs = asRs.GetAsRecordset( _
               adUseClient, _
               adLockOptimistic, _
               adOpenStatic)
      
   ' now do something with the recordset we have!
</pre>
<p>We should probably make the cursor and locking flags optional and have them default to standard  values which would make the code less above less complex in most cases.</p>

<p>We can add this interface to our simple data object's IDL and, in keeping with the ATL way of doing things, we can write a template that implements the interface for us.</p>

<p>The resulting implementation template looks something like this:</p>
<pre class="brush: cpp gutter: false">template &lt;
   class T, 
   const GUID* plibid = &amp;CComModule::m_libid, 
   WORD wMajor = 1,
   WORD wMinor = 0, 
   tihclass = CComTypeInfoHolder&gt;
class ATL_NO_VTABLE IGetAsADORecordsetImpl 
   :  public IDispatchImpl&lt;
         IGetAsADORecordset, 
         &amp;IID_IGetAsADORecordset, 
         plibid, 
         wMajor, 
         wMinor, 
         tihclass&gt;
{
   public:
  
      STDMETHOD(GetAsRecordset)(
         CursorLocationEnum cursorLocation,        // [in]
         LockTypeEnum lockType,                    // [in]
         CursorTypeEnum cursorType,                // [in]
         VARIANT *pvRecordset)                     // [out]
      {
         if (!pvRecordset)
         {
            return E_POINTER;
         }
         return E_NOTIMPL;
      }
};
</pre>
<p>It's fairly simple, but as the interface is a dispatch based and we inherit from <code>IDispatchImpl</code> we take extra template parameters and default them so that our users can adjust the functionality of the <code>IDispatch</code> implementation if they need to.</p>

<p>Of course we now have to connect our data object to the OLE DB provider so that it can access our tabular data and present it via ADO. As we'll be using ADO to connect to our OLE DB provider we must somehow pass the data object through the ADO call into the OLE DB provider. Luckily ADO providers a method to do this kind of thing in the form of a <code>Command</code> that takes a parameter, which can be anything that fits in a variant. We'll simply pass the <code>IUnknown</code> pointer to our data object as the parameter to our ADO <code>Command</code>.</p>

<p>We'll use the <code>#import</code> feature of VC++ to make the ADO coding easier. The resulting code is something like this:</p>
<pre class="brush: cpp gutter: false">ADODB::_ConnectionPtr Connection("ADODB.Connection");
  
Connection-&gt;Open(
   _bstr_t( L"Provider=SimpleDataObject.ConversionProvider.1"),
   _bstr_t(""), 
   _bstr_t(""), 
   -1);
  
ADODB::_CommandPtr Command("ADODB.Command");
  
Command-&gt;CommandText = _bstr_t("CONVERT");
  
pUnknown-&gt;AddRef();
  
ADODB::_ParameterPtr Param1 = Command-&gt;CreateParameter(
   _bstr_t("IUnknown"),
   ADODB::adIUnknown,
   ADODB::adParamInput,
   -1,
   _variant_t(pUnknown));
  
Command-&gt;Parameters-&gt;Append( Param1 ); 
Command-&gt;ActiveConnection = Connection;
  
CComQIPtr&lt;IDispatch&gt; spCommand = Command;
    
ADODB::_RecordsetPtr Rs1("ADODB.Recordset"); 
  
_variant_t vtEmpty (DISP_E_PARAMNOTFOUND, VT_ERROR);
  
Rs1-&gt;CursorLocation = (ADODB::CursorLocationEnum)cusorLocation; 
Rs1-&gt;CursorType = (ADODB::CursorTypeEnum)cursorType;
Rs1-&gt;LockType = (ADODB::LockTypeEnum)lockType;
Rs1-&gt;Open(
   _variant_t(spCommand), 
   vtEmpty, 
   ADODB::adOpenUnspecified, 
   ADODB::adLockUnspecified, 
   -1);
  
// Return the recordset in a variant...
  
pvRecordset-&gt;vt = VT_DISPATCH;
pvRecordset-&gt;pdispVal = (IDispatch*)Rs1.Detach();
</pre>
<p>Assuming our OLE DB Provider does its part then that's all we need to do from an ADO point of view.</p>

<p><b>Getting something to work... </b><br />
We can get the code above working to the point where it will return the standard "view of a file system" ADO recordset that the default, wizard-generated, OLE DB provider returns by adjusting the wizard-generated code very slightly.</p>

<p>First we need to add support for <code>ICommandWithParameters</code> as our command object incorporates a parameter. The implementation of this interface is very straight forward since our command is so simple. Of the three methods in <code>ICommandWithParameters</code>, only <code>SetParameterInfo</code> will ever get called for the command we use above - and this can simply return <code>S_OK</code>.</p>

<p>Once we support <code>ICommandWithParameters</code> our ADO calls will get all the way through to the OLE DB provider's rowset implementation of the <code>Execute</code> method. This needs to be adjusted to make it work with our command. The wizard-generated code is expecting the command text to be a file specification, it then returns a director listing of all files that match the specification. Later we will replace the entire rowset implementation with one of our own, but for now simply hacking the <code>szDir</code> variable to always be set to "*.*" will suffice.</p>

<p>Although this recordset does not represent the data stored in our simple data object in any way, it does at least provide a quick and dirty way of testing that the framework that we're building actually works.</p>

<p><b>Exposing the recordset interface from QI</b><br />
Now that we have all the code required to expose a recordset it would be nice to make it a 'real' interface on our data object rather than so obviously a separate view on the data. From a Visual Basic point of view it would be nicer if all we had to do was this:</p>
<pre class="brush: cpp gutter: false">   Dim dataObject as New MyDataObject
   Dim rs as ADODB.Recordset
   Set rs = dataObject
    
   ' now do something with the recordset we have!
</pre>
<p>This is actually reasonably easy. We need to create the ADO recordset as a tear-off interface when we're first asked for it and aggregate it into our data object. We can then add some <code>COM_INTERFACE_ENTRY_FUNC</code> macros to our COM Map to handle the various flavours of ADO recordset interfaces that we might be asked for and also to handle any other interfaces that the ADO recordset object is normally expected to expose... This can all be done inside our implementation of <code>IGetAsADORecordset</code> which leaves us only having to chain our object's interface map to the <code>IGetAsADORecordsetImpl</code> one...</p>

<p><b>Download</b><br />
The complete implementation of  our data object with support for returning an ADO recordset (full of unrelated file system information!) via <code>IGetAsADORecordset</code> and also via QI, can be found here.</p>

<p>The following source built using Visual Studio 6.0 SP3. Using the July 2000 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".</p>

<ul>
<li><a href="http://www.lenholgate.com/zips/SimpleDataObject.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject.zip']);">Simple Data Object</a></li>
<li><a href="http://www.lenholgate.com/zips/SimpleDataObject-2.zip" onclick="_gaq.push(['_trackEvent', 'Downloads', 'SimpleDataObject-2.zip']);">Simple Data Object with ADO recordset interfaces</a></li>
</ul>

<p><b>Revision History</b><br />
</p><ul><li>15th September 1999 - Initial revision at <a href="http://www.jetbyte.com">www.jetbyte.com</a>.</li>
<li>17th September 1999 - Hacked in a simple solution to a stupid cyclical reference counting problem. </li>
<li>22nd July 2000 - Bug fix, IGetAsADORecordsetImpl.h, line 77, <code>_Recordset15</code> should be <code>Recordset15</code>. Thanks to Nie Jiantao for reporting this.</li>
<li>2nd October 2000 - Fixed some build configuration errors. Thanks to Charles Finley for reporting these.</li>
<li>12th October 2005 - reprinted at <a href="http://www.lenholgate.com">www.lenholgate.com</a>.</li></ul><br />
<b>Other articles in the series</b><br />
<ul><li><b>Objects via ADO</b> - ADO seems to be the ideal way to expose tabular data from your own COM objects and the ATL OLE DB Provider templates can help!</li>
<li><a href="http://www.lenholgate.com/archives/000542.html">Custom Rowsets</a> - The ATL OLE DB Provider templates appear to rely on the fact that your data is kept in a simple array, but that's not really the case at all!</li>
<li><a href="http://www.lenholgate.com/archives/000543.html">IRowsetLocate and Bookmarks</a> - Adding bookmark functionality is relatively easy and it enables our ADO recordset to be used with a greater number of data bound controls.</li>
<li><a href="http://www.lenholgate.com/archives/000544.html">Updating data through an ADO recordset</a> - 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!</li>
<li><a href="http://www.lenholgate.com/archives/000545.html">Client Cursor Engine updates</a> - Making the ADO Client Cursor Engine believe that your rowset is updateable involves jumping through a few extra hoops...</li>
<li><a href="http://www.lenholgate.com/archives/000546.html">Disconnected Recordsets</a> - If you are going to use the client cursor engine then often it's a good idea to disconnect your recordset...</li></ul><p></p>]]>
    </content>
</entry>

</feed>



