Rowset Preservation
After a transaction ends, one of the following states is possible: The full functionality of the relevant rowset is preserved, or the rowset enters a zombie state. A preserved rowset retains all of its capabilities. A rowset that enters a zombie state is an object whose functionality is virtually lost. Specifically, zombie rowsets support only IUnknown operations and incrementing and decrementing reference counts on outstanding row and accessor handles. In general, all other operations on the zombie rowset produce the error E_UNEXPECTED.
The behavior of a rowset after a commit is determined by the property DBPROP_COMMITPRESERVE. If the value of this property is VARIANT_FALSE, the rowset enters a zombie state. However, if the value of DBPROP_COMMITPRESERVE is VARIANT_TRUE, the rowset remains fully functional after the commit, with all of its rows, bookmarks, pending changes, and so on still valid. Providers usually preserve pending changes on a preserved commit, although consumers should not rely on this behavior.
Similarly, the behavior of a rowset on an abort is determined by the property DBPROP_ABORTPRESERVE. If the value of DBPROP_ABORTPRESERVE is VARIANT_FALSE, rowsets created within the scope of the aborted transaction enter a zombie state after the abort. If the value of DBPROP_ABORTPRESERVE is VARIANT_TRUE, the rowsets remain fully functional, with all of their rows, bookmarks, pending changes, and so on still valid. Whether pending changes are retained or lost is provider-specific. In general, consumers should not expect pending changes to survive an abort. All changes made visible to the data source within the aborted transaction are undone.
Preserved rowsets are not automatically refreshed from the underlying data store. For example, if the transaction is aborted and the value of DBPROP_ABORTPRESERVE is VARIANT_TRUE, any pending changes are not undone in the rowset. The consumer must call IRowsetRefresh::RefreshVisibleData to synchronize the rowset with the data store.
Even if a rowset is created with preserving properties, it can enter a zombie state if other objects on which it depends are destroyed as a side effect of an abort. For example, a rowset can enter a zombie state if a table on which it was built was created as part of the same transaction that was aborted.
Following are some important notes about rowset preservation:
Providers are allowed, but not required, to support starting or enlisting in transactions while command, rowset, or row objects are open on a session. If a provider does not or cannot supply this support, it should return DB_E_OBJECTOPEN when ITransactionLocal::StartTransaction attempts to start, or when ITransactionJoin::JoinTransaction attempts to enlist, the transaction. See Distributed Transactions for more information about enlisting in a transaction.
Whether a provider returns DB_E_OBJECTOPEN may be related to whether the open object has been completely transmitted from the underlying data source or whether the provider is allowed to open additional connections to satisfy the request.
Rowset preservation is governed at the rowset level, as opposed to the session level, by the DBPROP_COMMITPRESERVE and DBPROP_ABORTPRESERVE properties.
If a provider allows objects to be open outside or prior to a transaction, those objects may be affected by a transaction commit or abort. However, a provider should zombie a rowset (that is, not preserve the rowset object) only if DBPROP_COMMITPRESERVE is FALSE and ITransaction::Commit is called or if DBPROP_ABORTPRESERVE is FALSE and ITransaction::Abort is called.
Because consumers might not always be aware that called code starts or enlists in transactions for them, consumers should explicitly set the preservation properties to TRUE to guarantee rowset preservation.