Converting Data Between Providers

In many applications, all providers synchronize exactly the same kind of data in the same format. For example, several database synchronization providers might synchronize and transfer data for a set of tables in ADO.NET data sets. However, data formats can be different in some situations. Consider an application that synchronizes contacts. The application uses custom providers written by two different developers. The data that these providers require is different in two ways:

  • Provider A transfers data as a byte stream, whereas provider B transfers data as an XML stream.

  • The data from provider A consists of three fields: FirstName, LastName, and PhoneNumber. The data from provider B includes FirstName and LastName, and divides PhoneNumber into PhoneNumber and AreaCode.

To address this scenario, Sync Framework lets you implement interfaces that convert data to the format that each provider requires. In this situation, you would write a converter that performs two conversions: the first converts a byte stream input to an XML output, and the second converts an XML input to a byte stream output. Sync Framework does not require that a converter be specified for both providers. The data conversion code that you implement is called by the synchronization session so that data conversion is transparent to the destination provider during the change application process.

Also consider a scenario in which a larger number of providers with different data formats need to synchronize data with each other. One approach is to write a converter for each pair of providers, but that can become unmanageable. A better alternative is to write converters for each provider that convert data to and from an intermediate format. In this situation, two data conversions are performed: from the source provider to the intermediate format, and from the intermediate format to the destination provider format.

The following table shows the interfaces and properties that Sync Framework provides for data conversion:

Managed Code Unmanaged Code

SyncDataConverter

ISyncDataConverter Interface

LocalDataConverter

ISyncDataConversionControl Interface

RemoteDataConverter

 

Use the following approach to convert data for each provider:

  1. Implement a converter for each provider.

  2. Specify the converters to be used during the synchronization session.

    • Managed code Specify the converters that you implemented as the two SyncOrchestrator properties: LocalDataConverter and RemoteDataConverter.

    • Unmanaged code Specify the converters that you implemented in the two ISyncDataConversionControl methods: SetSourceDataConverter and SetDestinationDataConverter.

In most situations, you will implement only two of the four available conversion methods and specify them for the synchronization session. The two methods related to data retriever conversion are required only if the data retriever that you use is not an implementation of IChangeDataRetriever (for managed code), or IAsynchronousDataRetriever or ISynchronousDataRetriever (for unmanaged code). For situations in which you convert data to an intermediate state, you must use an implementation of a Sync Framework data retriever interface.

Sync Framework supports data conversion for managed applications in which one or both providers are unmanaged. Be aware that the conversion process is always handled by managed objects. This can result in slower synchronization times than if the application is unmanaged.

Code Example

The following code example demonstrates how to synchronize two providers that require data conversion. In the Execute method, the code first instantiates the providers that will be synchronized and then instantiates two instances of the data conversion class that accepts an input and output format. In this example, the methods ConvertDataFromProviderFormat and ConvertDataToProviderFormat return data unchanged, but in a real application you would convert the input format to an appropriate output format.

public class SampleConversion
{
    public void Execute()
    {

        SyncOrchestrator orchestrator = new SyncOrchestrator();
        orchestrator.Direction = SyncDirectionOrder.Upload;
        orchestrator.LocalProvider = new SampleSyncProvider(localStore);
        orchestrator.RemoteProvider = new SampleSyncProvider(remoteStore);

        DataConverter<DataObject1, DataObject2> localConverter = new DataConverter<DataObject1, DataObject2>();

        DataConverter<DataObject2, DataObject3> remoteConverter = new DataConverter<DataObject2, DataObject3>();

        orchestrator.LocalDataConverter = localConverter;
        orchestrator.RemoteDataConverter = remoteConverter;

        orchestrator.Synchronize();
    }

    string localStore;
    string remoteStore;
}

public class DataConverter<SourceType, DestType> : SyncDataConverter
    where SourceType : IDataObject, new()
    where DestType : IDataObject, new()
{
    public DataConverter()
    {
    }

    public override object ConvertDataFromProviderFormat(LoadChangeContext loadChangeContext, object itemData)
    {
        SourceType dataObj = (SourceType)itemData;

        DestType returnData = new DestType();
        returnData.Data = dataObj.Data;

        return returnData;
    }

    public override object ConvertDataToProviderFormat(LoadChangeContext loadChangeContext, object itemData)
    {
        DestType dataObj = (DestType)itemData;

        SourceType returnData = new SourceType();
        returnData.Data = dataObj.Data;

        return returnData;
    }

    Type sourceType;
    Type destType;
}
Public Class SampleConversion
    Public Sub Execute()

        Dim orchestrator As New SyncOrchestrator()
        orchestrator.Direction = SyncDirectionOrder.Upload
        orchestrator.LocalProvider = New SampleSyncProvider(localStore)
        orchestrator.RemoteProvider = New SampleSyncProvider(remoteStore)

        Dim localConverter As New DataConverter(Of DataObject1, DataObject2)()

        Dim remoteConverter As New DataConverter(Of DataObject2, DataObject3)()

        orchestrator.LocalDataConverter = localConverter
        orchestrator.RemoteDataConverter = remoteConverter

        orchestrator.Synchronize()
    End Sub

    Private localStore As String
    Private remoteStore As String
End Class

Public Class DataConverter(Of SourceType As {IDataObject, New}, DestType As {IDataObject, New})
    Inherits SyncDataConverter
    Public Sub New()
    End Sub

    Public Overloads Overrides Function ConvertDataFromProviderFormat(ByVal loadChangeContext As LoadChangeContext, ByVal itemData As Object) As Object
        Dim dataObj As SourceType = DirectCast(itemData, SourceType)

        Dim returnData As New DestType()
        returnData.Data = dataObj.Data

        Return returnData
    End Function

    Public Overloads Overrides Function ConvertDataToProviderFormat(ByVal loadChangeContext As LoadChangeContext, ByVal itemData As Object) As Object
        Dim dataObj As DestType = DirectCast(itemData, DestType)

        Dim returnData As New SourceType()
        returnData.Data = dataObj.Data

        Return returnData
    End Function

    Private sourceType As Type
    Private destType As Type
End Class

The following code example defines the data retriever conversion methods TryConvertDataRetrieverFromProviderFormat and TryConvertDataRetrieverToProviderFormat. Both methods accept a data retriever and a list of changes. In order to perform data retriever conversion, they instantiate a ConvertedDataRetriever sample class that inherits from IChangeDataRetriever.

public override bool TryConvertDataRetrieverFromProviderFormat(
    object dataRetrieverIn,
    IEnumerable<ItemChange> changes,
    out object dataRetrieverOut)
{
    dataRetrieverOut = new ConvertedDataRetriever<SourceType, DestType>(dataRetrieverIn);
    return true;
}

public override bool TryConvertDataRetrieverToProviderFormat(
    object dataRetrieverIn,
    IEnumerable<ItemChange> changes,
    out object dataRetrieverOut)
{
    dataRetrieverOut = new ConvertedDataRetriever<DestType, SourceType>(dataRetrieverIn);
    return true;
}
Public Overloads Overrides Function TryConvertDataRetrieverFromProviderFormat(ByVal dataRetrieverIn As Object, ByVal changes As IEnumerable(Of ItemChange), ByRef dataRetrieverOut As Object) As Boolean
    dataRetrieverOut = New ConvertedDataRetriever(Of SourceType, DestType)(dataRetrieverIn)
    Return True
End Function

Public Overloads Overrides Function TryConvertDataRetrieverToProviderFormat(ByVal dataRetrieverIn As Object, ByVal changes As IEnumerable(Of ItemChange), ByRef dataRetrieverOut As Object) As Boolean
    dataRetrieverOut = New ConvertedDataRetriever(Of DestType, SourceType)(dataRetrieverIn)
    Return True
End Function

The following code example creates the ConvertedDataRetriever class and defines the LoadChangeData method and the IdFormats property. When converting data retrievers, any code that is required for data conversion must be contained in or called by the LoadChangeData method.

public class ConvertedDataRetriever<SourceType, DestType> : IChangeDataRetriever
    where SourceType : IDataObject, new()
    where DestType : IDataObject, new()
{
    public ConvertedDataRetriever(object dataRetriever)
    {
        this.dataRetriever = dataRetriever;
    }

    public SyncIdFormatGroup IdFormats
    {
        get
        {
            return ((IChangeDataRetriever)dataRetriever).IdFormats;
        }
    }

    public object LoadChangeData(LoadChangeContext loadChangeContext)
    {
        IChangeDataRetriever iRetriever = (IChangeDataRetriever)dataRetriever;

        object tempData = iRetriever.LoadChangeData(loadChangeContext);

        if (tempData != null)
        {
            return ConvertData(tempData);
        }

        return null;
    }

    private object ConvertData(object itemData)
    {
        SourceType dataObj = (SourceType)itemData;

        DestType returnData = new DestType();
        returnData.Data = dataObj.Data;

        return returnData;
    }

    object dataRetriever;
}
Public Class ConvertedDataRetriever(Of SourceType As {IDataObject, New}, DestType As {IDataObject, New})
    Implements IChangeDataRetriever
    Public Sub New(ByVal dataRetriever As Object)
        Me.dataRetriever = dataRetriever
    End Sub

    Public ReadOnly Property IdFormats() As SyncIdFormatGroup
        Get
            Return DirectCast(dataRetriever, IChangeDataRetriever).IdFormats
        End Get
    End Property

    Public Function LoadChangeData(ByVal loadChangeContext As LoadChangeContext) As Object
        Dim iRetriever As IChangeDataRetriever = DirectCast(dataRetriever, IChangeDataRetriever)

        Dim tempData As Object = iRetriever.LoadChangeData(loadChangeContext)

        If tempData IsNot Nothing Then
            Return ConvertData(tempData)
        End If

        Return Nothing
    End Function

    Private Function ConvertData(ByVal itemData As Object) As Object
        Dim dataObj As SourceType = DirectCast(itemData, SourceType)

        Dim returnData As New DestType()
        returnData.Data = dataObj.Data

        Return returnData
    End Function

    Private dataRetriever As Object
End Class

See Also

Concepts

Integrating Data from Different Providers