How to: Create an Unmanaged Synchronization Provider

This topic shows how to use an unmanaged language, such as C++, to create a Microsoft Sync Framework synchronization provider that synchronizes data from a custom data store.

This topic assumes a basic familiarity with C++ and COM concepts.

The examples in this topic focus on the following Sync Framework interfaces:

Understanding Synchronization Providers

A synchronization provider is a software component that represents a replica during synchronization. This enables the replica to synchronize its data with other replicas. For synchronization to occur, an application first creates a synchronization session object, connects it to two ISyncProvider objects, and starts the session. One of the providers represents the source replica. The source replica supplies metadata for changed items through its IKnowledgeSyncProvider::GetChangeBatch method and item data through an ISynchronousDataRetriever object. The other provider represents the destination replica. The destination replica receives metadata for changed items through its IKnowledgeSyncProvider::ProcessChangeBatch method and applies the changes to its item store by using a Sync Framework-supplied ISynchronousChangeApplier object together with its own ISynchronousChangeApplierTarget object.

For more information about the role of the synchronization provider, see Synchronization Providers.

Build Requirements

  • Synchronization.h: declarations for Sync Framework components.

    #include <synchronization.h>
    
  • Synchronizationerrors.h: custom error codes.

    #include <synchronizationerrors.h>
    
  • Synchronization.lib: import library.

Example

The example code in this topic shows how to implement the basic interface methods that are required for a replica to participate in a Sync Framework synchronization community, both as a source and as a destination. The replica in this example is an XML file, and the items to synchronize are XML nodes that are contained in this file. In the code, the XML nodes are represented by the IXMLDOMNode interface. This example also uses a custom metadata store that is implemented by using the Metadata Storage Service API. For information about Metadata Storage Service and other Sync Framework components, see this Microsoft Web site.

The metadata store and XML store are both declared as members of the provider class.

CMetadataMgr* m_pMetadataMgr;
CItemStore* m_pItemStore;

Implementing ISyncProvider and IKnowledgeSyncProvider

The entry point into the provider is the ISyncProvider interface. This interface is intended to function as a base class for other, more powerful provider interfaces. This example uses the IKnowledgeSyncProvider interface.

Declaring IKnowledgeSyncProvider

Add IKnowledgeSyncProvider to the class inheritance list.

class CXMLProvider : public IKnowledgeSyncProvider

Add the ISyncProvider methods to the class declaration.

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

Add the IKnowledgeSyncProvider methods to the class declaration.

STDMETHOD(BeginSession)(
    SYNC_PROVIDER_ROLE role,
    ISyncSessionState * pSessionState);

STDMETHOD(GetSyncBatchParameters)(
    ISyncKnowledge ** ppSyncKnowledge,
    DWORD * pdwRequestedBatchSize);

STDMETHOD(GetChangeBatch)(
    DWORD dwBatchSize,
    ISyncKnowledge * pSyncKnowledge,
    ISyncChangeBatch ** ppSyncChangeBatch,
    IUnknown ** ppUnkDataRetriever);
   
STDMETHOD(GetFullEnumerationChangeBatch)(
    DWORD dwBatchSize,
    const BYTE * pbLowerEnumerationBound,
    ISyncKnowledge * pSyncKnowledgeForDataRetrieval,
    ISyncFullEnumerationChangeBatch ** ppSyncChangeBatch,
    IUnknown ** ppUnkDataRetriever);

STDMETHOD(ProcessChangeBatch)(
    CONFLICT_RESOLUTION_POLICY resolutionPolicy,
    ISyncChangeBatch * pSourceChangeBatch,
    IUnknown * pUnkDataRetriever,
    ISyncCallback * pCallback,
    SYNC_SESSION_STATISTICS * pSyncSessionStatistics);

STDMETHOD(ProcessFullEnumerationChangeBatch)(
    CONFLICT_RESOLUTION_POLICY resolutionPolicy,
    ISyncFullEnumerationChangeBatch * pSourceChangeBatch,
    IUnknown * pUnkDataRetriever,
    ISyncCallback * pCallback,
    SYNC_SESSION_STATISTICS * pSyncSessionStatistics);

STDMETHOD(EndSession)(
    ISyncSessionState * pSessionState);

GetIdParameters Method

Sync Framework calls ISyncProvider::GetIdParameters on both the source and destination providers when the ISyncSession object is created. This method returns the ID format schema that is used by the provider. This schema must be the same for both providers. The implementation in this example uses a global constant because the ID formats are constant for the provider.

const ID_PARAMETERS c_idParams = 
{
    sizeof(ID_PARAMETERS), // dwSize
    { FALSE, sizeof(GUID) }, // replicaId
    { FALSE, sizeof(SYNC_GID) }, // itemId
    { FALSE, 1 }, // changeUnitId
};

Using a global constant makes implementing this method very easy.

STDMETHODIMP CXMLProvider::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = c_idParams;
        return S_OK;
    }
}

BeginSession Method

Sync Framework then calls IKnowledgeSyncProvider::BeginSession on both the source and destination providers. This method informs a provider that it is joining a synchronization session and passes the provider an object that contains session state information. This implementation stores the session state object.

STDMETHODIMP CXMLProvider::BeginSession(
    SYNC_PROVIDER_ROLE role,
    ISyncSessionState * pSessionState)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pSessionState)
    {
        hr = E_POINTER;
    }
    else
    {
        // This method should not be called twice.
        if (NULL != m_pSessionState || NULL == m_pMetadataMgr)
        {
            hr = SYNC_E_INVALID_OPERATION;
        }
        else
        {
            // Store the role and the session state object.
            m_role = role;

            pSessionState->AddRef();
            m_pSessionState = pSessionState;
            hr = S_OK;
        }
    }

    return hr;
}

GetSyncBatchParameters Method

Sync Framework then calls IKnowledgeSyncProvider::GetSyncBatchParameters on the destination provider. This method retrieves the number of changes the source provider should include in a change batch, and obtains the destination provider's current knowledge. The implementation extracts the knowledge from the metadata store, and sets the batch size to 10.

STDMETHODIMP CXMLProvider::GetSyncBatchParameters(
    ISyncKnowledge ** ppSyncKnowledge,
    DWORD * pdwRequestedBatchSize)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == ppSyncKnowledge || NULL == pdwRequestedBatchSize)
    {
        hr = E_POINTER;
    }
    else
    {
        _ASSERT(NULL != m_pMetadataMgr);
    
        *pdwRequestedBatchSize = 10;

        hr = m_pMetadataMgr->GetKnowledge(ppSyncKnowledge);
    }

    return hr;
}

GetChangeBatch Method

The synchronization session starts in earnest when Sync Framework calls IKnowledgeSyncProvider::GetChangeBatch on the source provider. This method retrieves a batch of changes to send to the destination provider and also returns the data retriever interface. The destination provider uses this interface to retrieve item data for changes that were applied to the destination replica. Sync Framework calls GetChangeBatch repeatedly until the last batch is sent. The source provider indicates that a batch is the last batch by calling the ISyncChangeBatchBase::SetLastBatch method. This implementation delegates the change enumeration task to the GetChangeBatch method of the metadata store. The XML item store object implements a data retriever interface; therefore, its IUnknown interface is returned.

STDMETHODIMP CXMLProvider::GetChangeBatch(
    DWORD dwBatchSize,
    ISyncKnowledge * pSyncKnowledge,
    ISyncChangeBatch ** ppSyncChangeBatch,
    IUnknown ** ppUnkDataRetriever)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pSyncKnowledge || NULL == ppSyncChangeBatch || NULL == ppUnkDataRetriever)
    {
        hr = E_POINTER;
    }
    else
    {
        _ASSERT(NULL != m_pMetadataMgr);
        hr = m_pMetadataMgr->GetChangeBatch(dwBatchSize, pSyncKnowledge, ppSyncChangeBatch);
        if (SUCCEEDED(hr))
        {
            hr = m_pItemStore->QueryInterface(IID_IUnknown, (void**)ppUnkDataRetriever);
        }
    }

    return hr;
}

The GetChangeBatch method implemented by the metadata store enumerates the items in the metadata store and checks the version of each one against the destination's knowledge. If the destination replica does not know about a change, the change is added to the change batch that is returned.

STDMETHODIMP CMetadataMgr::GetChangeBatch(
    DWORD dwBatchSize,
    ISyncKnowledge *pSyncKnowledge,
    ISyncChangeBatch ** ppSyncChangeBatch)
{
    HRESULT hr = E_UNEXPECTED;

    ISyncChangeBatch* pChangeBatch = NULL;
    ISyncKnowledge* pMappedDestKnowledge = NULL;
    ISyncKnowledge* pSourceKnowledge = NULL;

    if (NULL == pSyncKnowledge || NULL == ppSyncChangeBatch)
    {
        hr = E_POINTER;
    }
    else
    {
        // Get our (source) knowledge object, map the remote (destination) knowledge for local use, 
        // and get our replica ID.
        GUID guidReplicaID;
        hr = GetKnowledge(&pSourceKnowledge);
        if (SUCCEEDED(hr))
        {
            hr = pSourceKnowledge->MapRemoteToLocal(pSyncKnowledge, &pMappedDestKnowledge);
            if (SUCCEEDED(hr))
            {
                ULONG cbID = sizeof(guidReplicaID);
                hr = GetReplicaId((BYTE*)&guidReplicaID, &cbID);
            }
        }

        if (SUCCEEDED(hr))
        {
            // Create a new change batch object.  We'll fill this object with changes to send.
            IProviderSyncServices* pProvSvc = NULL;
            // This helper function creates and initializes the IProviderSyncServices interface.
            hr = GetProviderSyncServices(&c_idParams, &pProvSvc);
            if (SUCCEEDED(hr))
            {
                hr = pProvSvc->CreateChangeBatch(pSyncKnowledge, NULL, &pChangeBatch);            

                pProvSvc->Release();
                pProvSvc = NULL;
            }
        }

        // Enumerate the items in our store and add new changes to the change batch.
        if (SUCCEEDED(hr))
        {
            // Begin an unordered group in our change batch. All change items will be added to this group.
            hr = pChangeBatch->BeginUnorderedGroup();
            if (SUCCEEDED(hr))
            {
                ULONG cFetched = 1;
                IItemMetadata* pItemMeta = NULL;
                SYNC_GID gidItem;
                ULONG cbgid = sizeof(gidItem);
                SYNC_VERSION verCur;
                SYNC_VERSION verCreate;
                hr = Reset();
                while (S_OK == hr)
                {
                    hr = Next(1, &pItemMeta, &cFetched);
                    if (S_OK == hr)
                    {
                        hr = pItemMeta->GetGlobalId((BYTE*)&gidItem, &cbgid);
                        if (SUCCEEDED(hr))
                        {
                            hr = pItemMeta->GetChangeVersion(&verCur);
                            if (SUCCEEDED(hr))
                            {
                                // Find out whether the destination already knows about this change.
                                hr = pMappedDestKnowledge->ContainsChange((BYTE*)&guidReplicaID,
                                    (BYTE*)&gidItem, &verCur);
                                if (S_FALSE == hr)
                                {
                                    // S_FALSE means the destination does not know about the 
                                    // change, so add it to the change batch.
                                    DWORD dwFlags = 0;
                                    BOOL fTomb = 0;
                                    hr = pItemMeta->GetIsDeleted(&fTomb);
                                    if (fTomb)
                                    {
                                        dwFlags = SYNC_CHANGE_FLAG_DELETED;                            
                                    }

                                    hr = pItemMeta->GetCreationVersion(&verCreate);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pChangeBatch->AddItemMetadataToGroup((BYTE*)&guidReplicaID, 
                                            (BYTE*)&gidItem, &verCur, &verCreate, dwFlags, 0, NULL);
                                    }
                                }
                            }
                        }

                        pItemMeta->Release();
                    }
                }
            }

            if (SUCCEEDED(hr))
            {
                // We always send the entire set of changes, so every batch is the last batch. 
                // If this flag is not set Sync Framework will call GetChangeBatch again.
                hr = pChangeBatch->SetLastBatch();
            }

            if (SUCCEEDED(hr))
            {
                // Close the change batch group that contains our changes.
                hr = pChangeBatch->EndUnorderedGroup(pSourceKnowledge, TRUE);
            }
        }

        if (NULL != pChangeBatch)
        {
            if (SUCCEEDED(hr))
            {
                // Return the change batch we've constructed.  This will be sent to the 
                // destination provider.
                *ppSyncChangeBatch = pChangeBatch;
            }
            else
            {
                pChangeBatch->Release();            
            }
        }

        if (NULL != pMappedDestKnowledge)
        {
            pMappedDestKnowledge->Release();
        }
        if (NULL != pSourceKnowledge)
        {
            pSourceKnowledge->Release();
        }
    }

    return hr;
}

ProcessChangeBatch Method

After Sync Framework has obtained a batch of changes from the source provider by calling its GetChangeBatch method, Sync Framework calls IKnowledgeSyncProvider::ProcessChangeBatch on the destination provider. This method applies the changes to the destination replica. This method is called one time for each batch that is retrieved by using GetChangeBatch from the source provider. This implementation uses the GetItemBatchVersions method of the metadata store to obtain local version information for items from the source provider. It then creates an ISynchronousNotifyingChangeApplier object implemented by Sync Framework and calls its ISynchronousNotifyingChangeApplier::ApplyChanges method.

STDMETHODIMP CXMLProvider::ProcessChangeBatch(
    CONFLICT_RESOLUTION_POLICY resolutionPolicy,
    ISyncChangeBatch * pSourceChangeBatch,
    IUnknown * pUnkDataRetriever,
    ISyncCallback * pCallback,
    SYNC_SESSION_STATISTICS * pSyncSessionStatistics)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pSourceChangeBatch || NULL == pUnkDataRetriever || NULL == pSyncSessionStatistics)
    {
        hr = E_POINTER;
    }
    else
    {
        IEnumSyncChanges* pDestinationChangeEnum = NULL;

        _ASSERT(NULL != m_pMetadataMgr);

        // Obtain the local (destination) versions for the items in the source change batch.
        hr = m_pMetadataMgr->GetItemBatchVersions(pSourceChangeBatch, &pDestinationChangeEnum);
        if (SUCCEEDED(hr))
        {
            IProviderSyncServices* pProviderSvc = NULL;
            hr = GetProviderSyncServices(&c_idParams, &pProviderSvc);
            if (SUCCEEDED(hr))
            {
                // Create a standard change applier from Sync Framework.
                ISynchronousNotifyingChangeApplier* pChangeApplier = NULL;
                hr = pProviderSvc->CreateChangeApplier(IID_ISynchronousNotifyingChangeApplier,
                    (void**)&pChangeApplier);
                if (SUCCEEDED(hr))
                {
                    ISyncKnowledge* pDestinationKnowledge = NULL;
                    hr = m_pMetadataMgr->GetKnowledge(&pDestinationKnowledge);
                    if (SUCCEEDED(hr))
                    {
                        // Have the change applier process the change batch and apply changes.
                        // This method will call the change applier target methods to save
                        // changes and conflicts.  It will also pass the data retriever
                        // interface to the change applier target so it can retrieve item data.
                        hr = pChangeApplier->ApplyChanges(resolutionPolicy, pSourceChangeBatch, 
                            pUnkDataRetriever, pDestinationChangeEnum, pDestinationKnowledge, 
                            NULL, this, m_pSessionState, pCallback);
                        
                        pDestinationKnowledge->Release();
                    }

                    pChangeApplier->Release();
                }

                pProviderSvc->Release();
            }

            pDestinationChangeEnum->Release();
        }
    }

    return hr;
}

The GetItemBatchVersions method of the metadata store enumerates the changes sent in the change batch from the source provider. If an item is in the destination metadata, its version information is added to a new batch created expressly to hold the version information. If an item does not exist in the destination metadata, it is flagged as a new item in the version batch. The method then returns the version batch.

STDMETHODIMP CMetadataMgr::GetItemBatchVersions(
    ISyncChangeBatch * pRemoteSyncChangeBatch,
    IEnumSyncChanges ** ppLocalVersionsEnum)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pRemoteSyncChangeBatch || NULL == ppLocalVersionsEnum)
    {
        hr = E_POINTER;
    }
    else
    {
        IProviderSyncServices* pProvSvc;
        hr = GetProviderSyncServices(&c_idParams, &pProvSvc);
        if (SUCCEEDED(hr))
        {
            IDestinationChangeVersionsBuilder* pDestChangeBuilder = NULL;
            hr = pProvSvc->CreateDestinationChangeVersionsBuilder(&pDestChangeBuilder);
            if (SUCCEEDED(hr))
            {
                IEnumSyncChanges* pRemoteEnum = NULL;
                hr = pRemoteSyncChangeBatch->GetChangeEnumerator(&pRemoteEnum);
                if (SUCCEEDED(hr))
                {
                    ULONG cFetched;

                    ISyncChange* pChange;
                    SYNC_GID gidItem;
                    DWORD cbID = sizeof(gidItem);
                    DWORD dwFlags;
                    SYNC_VERSION verCurrent;
                    SYNC_VERSION verCreation;
                    HRESULT hrEnum = S_OK;
                    while (S_OK == hrEnum && SUCCEEDED(hr))
                    {
                        pChange = NULL;
                        hrEnum = pRemoteEnum->Next(1, &pChange, &cFetched);
                        if (S_OK == hrEnum)
                        {
                            hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbID);
                            if (SUCCEEDED(hr))
                            {
                                // Try to find the item in the local (destination) metadata.
                                IItemMetadata* pItem = NULL;
                                hr = FindItemMetadataByGlobalId((BYTE*)&gidItem, &pItem);
                                if (S_OK == hr)
                                {
                                    // S_OK means the item exists in our local store.
                                    // Extract its version and tombstone information.
                                    dwFlags = 0;

                                    BOOL fTombstone = FALSE;
                                    hr = pItem->GetIsDeleted(&fTombstone);
                                    if (SUCCEEDED(hr))
                                    {
                                        if (fTombstone)
                                        {
                                            dwFlags = SYNC_CHANGE_FLAG_DELETED;
                                        }
                                    }

                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pItem->GetChangeVersion(&verCurrent);
                                        if (SUCCEEDED(hr))
                                        {
                                            hr = pItem->GetCreationVersion(&verCreation);                                            
                                        }
                                    }

                                    pItem->Release();
                                }
                                else if (S_FALSE == hr)
                                {
                                    // S_FALSE means this item does not exist in our local store.
                                    // Set versions to 0 and flag it as a new item.
                                    verCurrent.dwLastUpdatingReplicaKey = 0;
                                    verCurrent.ullTickCount = 0;
                                    verCreation.dwLastUpdatingReplicaKey = 0;
                                    verCreation.ullTickCount = 0;
                                    dwFlags = SYNC_CHANGE_FLAG_DOES_NOT_EXIST;
                                }

                                if (SUCCEEDED(hr))
                                {
                                    // Add the item to the batch of destination versions.
                                    GUID guidReplicaID = GUID_NULL;
                                    ULONG cbID = sizeof(guidReplicaID);
                                    hr = GetReplicaId((BYTE*)&guidReplicaID, &cbID);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pDestChangeBuilder->AddItemMetadata((BYTE*)&guidReplicaID,
                                            (BYTE*)&gidItem, &verCurrent, &verCreation, dwFlags, NULL);
                                    }
                                }
                            }

                            pChange->Release();
                        }
                    }

                    if (FAILED(hrEnum))
                    {
                        hr = hrEnum;                    
                    }

                    pRemoteEnum->Release();                
                }

                if (SUCCEEDED(hr))
                {
                    hr = pDestChangeBuilder->GetChangeEnumerator(ppLocalVersionsEnum);               
                }

                pDestChangeBuilder->Release();
            }

            pProvSvc->Release();        
        }
    }

    return hr;
}

EndSession Method

After the source provider sends its last batch and the destination provider has applied the changes to its data store, Sync Framework calls IKnowledgeSyncProvider::EndSession on both the source and destination providers. This method informs a provider that it is leaving a synchronization session and should free any resources that are associated with the session. This implementation frees the session state object that it stored in the BeginSession call.

STDMETHODIMP CXMLProvider::EndSession(
    ISyncSessionState * pSessionState)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == m_pSessionState)
    {
        hr = SYNC_E_INVALID_OPERATION;
    }
    else
    {
        m_pSessionState->Release();
        m_pSessionState = NULL;
        hr = S_OK;
    }

    return hr;
}

Methods That Are Not Implemented

The following methods are not required because this sample never removes items marked as deleted in the metadata store. These methods can return E_NOTIMPL:

Implementing ISynchronousNotifyingChangeApplierTarget

This interface is provided to Sync Framework when the destination provider calls the ISynchronousNotifyingChangeApplier::ApplyChanges method, typically in the ProcessChangeBatch method. ISynchronousNotifyingChangeApplierTarget contains methods that are called during change application. These methods are only called on the destination provider.

Declaring ISynchronousNotifyingChangeApplierTarget

Add ISynchronousNotifyingChangeApplierTarget to your class inheritance list.

class CXMLProvider : public IKnowledgeSyncProvider
    , ISynchronousNotifyingChangeApplierTarget

Add the ISynchronousNotifyingChangeApplierTarget methods to your class declaration.

STDMETHOD(GetDataRetriever)(
    IUnknown ** ppDataRetriever);

STDMETHOD(GetCurrentTickCount)(
    ULONGLONG * pTickCount);

STDMETHOD(GetDestinationVersion)(
    ISyncChange * pSourceChange,
    ISyncChange ** ppDestinationVersion);

STDMETHOD(SaveChange)(
    SYNC_SAVE_ACTION  ssa,
    ISyncChange * pChange,
    ISaveChangeContext * pSaveContext);

STDMETHOD(SaveChangeWithChangeUnits)(
    ISyncChange * pChange,
    ISaveChangeWithChangeUnitsContext * pSaveContext);

STDMETHOD(SaveConflict)(
    ISyncChange * pChange,
    IUnknown * pUnkData,
    ISyncKnowledge * pConflictKnowledge);

STDMETHOD(SaveKnowledge)(
    ISyncKnowledge * pSyncKnowledge,
    IForgottenKnowledge * pForgottenKnowledge);

GetIdParameters Method

Sync Framework calls ISynchronousNotifyingChangeApplierTarget::GetIdParameters to retrieve the ID format schema of the provider. This example uses the same class to implement both IKnowledgeSyncProvider and ISynchronousNotifyingChangeApplierTarget. Therefore, this implementation is the same as that for ISyncProvider::GetIdParameters.

STDMETHODIMP CXMLProvider::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = c_idParams;
        return S_OK;
    }
}

GetCurrentTickCount

Sync Framework calls ISynchronousNotifyingChangeApplierTarget::GetCurrentTickCount to increment and retrieve the tick count for the replica. This implementation calls the GetNextTickCount method of the metadata store.

STDMETHODIMP CXMLProvider::GetCurrentTickCount(
    ULONGLONG * pTickCount)
{
    _ASSERT(NULL != m_pMetadataMgr);
    return m_pMetadataMgr->GetNextTickCount(pTickCount);
}

The GetNextTickCount method of the metadata store increments the tick count of the replica and returns it.

STDMETHODIMP CMetadataMgr::GetNextTickCount(
     ULONGLONG * pNextTickCount)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pNextTickCount)
    {
        hr = E_POINTER;
    }
    else
    {
        // Get the local tick count, increment it, store it, and return it.
        ULONGLONG ullTickCount = -1;
        hr = GetTickCount(&ullTickCount);
        if (SUCCEEDED(hr))
        {
            ++ullTickCount;
            hr = SetTickCount(ullTickCount);
            if (SUCCEEDED(hr))
            {
                *pNextTickCount = ullTickCount;            
            }
        }
    }

    return hr;
}

SaveChange

During change application, Sync Framework calls ISynchronousNotifyingChangeApplierTarget::SaveChange for each change that is to be applied to the destination replica. This implementation appropriately handles new items, changed items, and deleted items; and updates both the item data in the item store and the item metadata in the metadata store.

STDMETHODIMP CXMLProvider::SaveChange(
    SYNC_SAVE_ACTION ssa,
    ISyncChange * pChange,
    ISaveChangeContext * pSaveContext)
{
    HRESULT hr = E_UNEXPECTED;

    _ASSERT(NULL != m_pItemStore);

    if (NULL == pChange || NULL == pSaveContext)
    {
        hr = E_POINTER;
    }
    else
    {
        // First save or delete the item data itself.
        switch (ssa)
        {
        case SSA_DELETE_AND_REMOVE_TOMBSTONE:
        {
            // This sample does not track forgotten knowledge and so cannot properly
            // handle this action.
            hr = E_UNEXPECTED;
            break;
        }

        case SSA_CREATE:
        case SSA_UPDATE_VERSION_AND_DATA:
        case SSA_UPDATE_VERSION_AND_MERGE_DATA:
        {
            // Save the item in the data store.

            // This IUnknown interface is the interface returned by the data retriever's
            // LoadChangeData method.
            IUnknown* pUnk = NULL;
            hr = pSaveContext->GetChangeData(&pUnk);
            if (S_OK == hr)
            {
                // The item is an XML node.
                IXMLDOMNode* pNode = NULL;
                hr = pUnk->QueryInterface(__uuidof(pNode), (void**)&pNode);
                if (SUCCEEDED(hr))
                {
                    // Have the data store save the item.
                    hr = m_pItemStore->SaveItem(pChange, pNode);

                    pNode->Release();
                }

                pUnk->Release();
            }

            break;
        }

        case SSA_DELETE_AND_STORE_TOMBSTONE:
        {
            // Delete the item from the data store.
            hr = m_pItemStore->DeleteItem(pChange);
        }
            break;

        case SSA_UPDATE_VERSION_ONLY:
        {
            // Update the version only, so nothing to do in the data store.
            hr = S_OK;
        }
            break;

        default:
            hr = E_INVALIDARG;
        }

        // Now update the metadata for the item in the metadata store.
        if (SUCCEEDED(hr))
        {
            SYNC_GID gidItem;
            DWORD cbItemID = sizeof(gidItem);
            hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbItemID);
            if (SUCCEEDED(hr))
            {
                // Save the item metadata to the metadata store.
                // First extract the information from the change.
                GUID guidReplicaID;
                ULONG cbReplicaID = sizeof(guidReplicaID);
                hr = m_pMetadataMgr->GetReplicaId((BYTE*)&guidReplicaID, &cbReplicaID);
                if (SUCCEEDED(hr))
                {
                    SYNC_VERSION verCurrent;
                    hr = pChange->GetChangeVersion((BYTE*)&guidReplicaID, &verCurrent);
                    if (SUCCEEDED(hr))
                    {
                        SYNC_VERSION verCreation;
                        hr = pChange->GetCreationVersion((BYTE*)&guidReplicaID, &verCreation);
                        if (SUCCEEDED(hr))
                        {
                            DWORD dwFlags;
                            hr = pChange->GetFlags(&dwFlags);
                            if (SUCCEEDED(hr))
                            {
                                // Try to find the item in the metadata store.
                                IItemMetadata* pItem = NULL;
                                hr = m_pMetadataMgr->FindItemMetadataByGlobalId((BYTE*)&gidItem, 
                                    &pItem);
                                if (S_FALSE == hr)
                                {
                                    // S_FALSE means the item does not exist in the metadata store.
                                    // Therefore it must be a new item.  Create it and set its
                                    // creation version.
                                    hr = m_pMetadataMgr->CreateNewItemMetadata(&pItem);
                                    if (SUCCEEDED(hr))
                                    {
                                        hr = pItem->SetGlobalId((BYTE*)&gidItem);
                                        if (SUCCEEDED(hr))
                                        {
                                            hr = pItem->SetCreationVersion(&verCreation);
                                        }
                                    }
                                }

                                // Set the item's change version and tombstone status.
                                if (SUCCEEDED(hr))
                                {
                                    if (dwFlags & SYNC_CHANGE_FLAG_DELETED)
                                    {
                                        hr = pItem->MarkAsDeleted(&verCurrent);
                                    }
                                    else
                                    {
                                        hr = pItem->SetChangeVersion(&verCurrent);
                                    }
                                }

                                // Commit the item change and update the knowledge.
                                if (SUCCEEDED(hr))
                                {
                                    hr = m_pMetadataMgr->SaveItemMetadata(pItem);
                                    if (SUCCEEDED(hr))
                                    {
                                        ISyncKnowledge* pUpdatedKnowledge = NULL;
                                        IForgottenKnowledge* pUpdatedForgottenKnowledge = NULL;
                                        hr = pSaveContext->GetKnowledgeForScope(&pUpdatedKnowledge, &pUpdatedForgottenKnowledge);
                                        if (SUCCEEDED(hr))
                                        {
                                            hr = m_pMetadataMgr->SetKnowledge(pUpdatedKnowledge);

                                            pUpdatedKnowledge->Release();

                                            if (NULL != pUpdatedForgottenKnowledge)
                                            {
                                                // This sample does not use forgotten knowledge, so it is an error to receive
                                                // forgotten knowledge from the save context.
                                                hr = E_UNEXPECTED;

                                                pUpdatedForgottenKnowledge->Release();
                                            }
                                        }
                                    }
                                }

                                if (NULL != pItem)
                                {
                                    pItem->Release();                                    
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return hr;
}

SaveKnowledge

After processing each change batch, Sync Framework calls ISynchronousNotifyingChangeApplierTarget::SaveKnowledge so that the destination provider can save the knowledge that contains the new changes. This implementation saves the knowledge object to the metadata store and overwrites previously existing knowledge.

STDMETHODIMP CXMLProvider::SaveKnowledge(
    ISyncKnowledge * pSyncKnowledge,
    IForgottenKnowledge * pForgottenKnowledge)
{
    HRESULT hr = E_UNEXPECTED;

    _ASSERT(NULL != m_pMetadataMgr);

    if (NULL == pSyncKnowledge)
    {
        hr = E_POINTER;    
    }
    else if (NULL != pForgottenKnowledge)
    {
        // This sample does not support forgotten knowledge, so it is an error to receive it in this method.bb
        hr = E_INVALIDARG;
    }
    else
    {
        hr = m_pMetadataMgr->SetKnowledge(pSyncKnowledge);
    }
    
    return hr;
}

Methods Not Implemented

The following methods are not required for basic synchronization scenarios and can return only E_NOTIMPL:

Implementing ISynchronousDataRetriever

ISynchronousDataRetriever is returned to Sync Framework by the source provider in response to the GetChangeBatch call. ISynchronousDataRetriever is sent to the destination provider in the ProcessChangeBatch call, where it is typically passed on to the ApplyChanges method of a change applier. The change applier then calls ISynchronousDataRetriever::LoadChangeData to obtain an IUnknown interface that represents the item data. The change applier passes this interface to the SaveChange method of the destination provider. The destination provider uses this IUnknown interface to retrieve item data for new or changed items and applies the item data to the destination replica.

Declaring ISynchronousDataRetriever

Add ISynchronousDataRetriever to the class inheritance list.

class CItemStore : public ISynchronousDataRetriever

Add the ISynchronousDataRetriever methods to the class declaration.

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

STDMETHOD(LoadChangeData)(
    ILoadChangeContext * pLoadChangeContext,
    IUnknown ** ppUnkData);

GetIdParameters Method

Sync Framework calls ISynchronousDataRetriever::GetIdParameters to retrieve the ID format schema of the provider. This implementation is basically the same as that for ISyncProvider::GetIdParameters.

STDMETHODIMP CItemStore::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = c_idParams;
        return S_OK;
    }
}

LoadChangeData Method

During change application, Sync Framework calls ISynchronousDataRetriever::LoadChangeData to obtain an IUnknown interface that the destination provider can use to retrieve item data. This implementation finds the item in the item store, clones it, and returns its IUnknown interface.

STDMETHODIMP CItemStore::LoadChangeData(
    ILoadChangeContext * pLoadChangeContext,
    IUnknown ** ppUnkData)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pLoadChangeContext || NULL == ppUnkData)
    {
        hr = E_POINTER;    
    }
    else
    {
        // Find the item in the data store, clone it, and return its IUnknown interface.
        ISyncChange* pChange = NULL;
        hr = pLoadChangeContext->GetSyncChange(&pChange);
        if (SUCCEEDED(hr))
        {
            SYNC_GID gidItem;
            DWORD cbID = sizeof(gidItem);
            hr = pChange->GetRootItemId((BYTE*)&gidItem, &cbID);
            if (SUCCEEDED(hr))
            {
                IXMLDOMNode* pNodeItem = NULL;
                hr = FindItem(&gidItem, &pNodeItem);
                if (SUCCEEDED(hr))
                {
                    IXMLDOMNode* pNodeClone = NULL;
                    hr = pNodeItem->cloneNode(TRUE, &pNodeClone);
                    if (SUCCEEDED(hr))
                    {
                        hr = pNodeClone->QueryInterface(IID_IUnknown, (void**)ppUnkData);

                        pNodeClone->Release();
                    }

                    pNodeItem->Release();                
                }
            }

            pChange->Release();
        }
    }

    return hr;
}

Next Steps

Now that you have created a synchronization provider, you might want to create an application that can host the synchronization session and connect it to the provider. For information about how to do this, see How to: Create an Unmanaged Synchronization Application.

Another next step that you could take is to enhance the provider to handle change units. For more information about change units, see Synchronizing Change Units.

You might also want to create a custom metadata store. For more information about how to handle synchronization metadata, see Metadata Management.

See Also

Reference

ISyncProvider Interface
IKnowledgeSyncProvider Interface
ISynchronousNotifyingChangeApplierTarget Interface
ISynchronousDataRetriever Interface
ID_PARAMETERS Structure
ISynchronousNotifyingChangeApplier Interface

Concepts

Synchronization Providers
Sync Framework Core Components