How to: Create a Managed Simple Provider
This topic describes important parts of the "Sync101 using Simple Sync Provider" sample application that is included with the Sync Framework SDK. This application demonstrates how to create and synchronize anchor-based and full enumeration simple providers. The sample application has three classes:
MyFullEnumerationSimpleSyncProvider, which derives from FullEnumerationSimpleSyncProvider.
MyAnchorEnumerationSimpleSyncProvider, which derives from AnchorEnumerationSimpleSyncProvider.
MySimpleDataStore is an in-memory item data store. MySimpleDataStore is a class that is used for this sample; it is not a part of Sync Framework.
This topic describes the following parts of the application:
Creating the metadata store
Identifying items in the item store and metadata store
Enumerating items and loading data
Synchronizing two providers
The topic also briefly describes how to filter data and perform local-only deletes.
Creating the Metadata Store
Each replica requires a metadata store, which for simple providers is an instance of SqlMetadataStore. The following code example specifies options for a store in the constructor of MyFullEnumerationSimpleSyncProvider:
The following code example creates the store:
The following code returns this store as a provider property:
Identifying Items in the Item Store and Metadata Store
To synchronize an item, Sync Framework must be able to identify the item in the item store and map that identity to an internal ID in the metadata store. It must also be able to determine if the item version has changed since the last synchronization session. If the version has changed and the destination replica does not already contain that version of an item, the item should be synchronized. If changes are synchronized at the level of a change unit instead of an item, Sync Framework must be able to identify the change unit and its version. A change unit represents a sub-item change, such as the telephone number field in an item that represents a contact. This sample does not use change units.
Specifying the Format of Metadata Store IDs
The following code defines the constructor for MyFullEnumerationSimpleSyncProvider and the IdFormats property. This enables the Sync Framework runtime to determine what format the metadata store uses for IDs. If flexible IDs are not used, Sync Framework uses a fixed format to identify replicas, items, and change units. If flexible IDs are used, ISimpleSyncProviderIdGenerator methods are used to generate IDs.
Specifying Item Fields and the Metadata Schema
Sync Framework maps item store data, or additional metadata that you create, to internal metadata store IDs and versions by using an ItemMetadataSchema object that is exposed by the MetadataSchema property. The following code examples provide input for the ItemMetadataSchema object. The constants in the sample code define an integer value for each column in the item store. These values are used when creating the custom field definitions and identity rules for the ItemMetadataSchema object.
The ItemMetadataSchema object exposes three properties:
Custom fields are fields in the metadata store that are identified by integers. If an application requires a friendly name for one or more fields, it should map the integer to a name. Custom fields are defined for two reasons: to identify items, and to provide version information about those items. Version fields enable Sync Framework to determine if an item or change unit has changed. In this example, the version fields contain the actual data from the item store, so there is a field for each field in the item store. This one-to-one correspondence is not required, nor is it efficient. A more practical solution would be to take a hash of the item fields and store that in a single custom field.
The identity rule specifies which custom field or fields should be used to identify an item. In this case, the CUSTOM_FIELD_ID field (field 0) is used.
ChangeUnitVersionDefinitions (not used in this sample)
If change units are used, you must define version fields for the change units. There is no requirement that there be a one-to-one mapping between change units and version information, or that the actual data must be stored. Change units can also span multiple fields. For example, this application could specify that Zip and Phone are a change unit and that Guid is another change unit. For Guid, you might use the actual data, and for the other change unit a hash of the Zip and Phone fields or some other mechanism to determine version.
Some of the methods that work with item store data, such as InsertItem(Object, IEnumerableSyncId, RecoverableErrorReportingContext, ItemFieldDictionary, Boolean), require a collection of ItemField objects that represent each field. The ItemFieldDictionary objects that are parameters to these methods have the same index values as those specified in the CustomFieldDefinition objects.
Enumerating Items and Loading Data
Sync Framework must be able to enumerate items in the source item store, detect if items or change units have changed, and then load the changed data so that it can be applied to the destination store. Change detection is handled by the Sync Framework runtime, but change enumeration and data loading are store-specific and are handled for full enumeration providers by implementing EnumerateItems(FullEnumerationContext) and LoadChangeData(ItemFieldDictionary, IEnumerableSyncId, RecoverableErrorReportingContext). The following code example returns a list of items that are enumerated from the MySimpleDataStore object:
The following code example returns an object that contains one of the data changes that was enumerated by EnumerateItems(FullEnumerationContext). Sync Framework calls this method until all changes have been loaded.
Applying Inserts, Updates, and Deletes
After Sync Framework has detected and loaded changes from the source, it must apply those changes and corresponding metadata changes to the destination replica. Metadata changes at the destination are handled by Sync Framework, but applying data changes is store-specific and is handled by implementing the following methods: DeleteItem(ItemFieldDictionary, RecoverableErrorReportingContext, Boolean), InsertItem(Object, IEnumerableSyncId, RecoverableErrorReportingContext, ItemFieldDictionary, Boolean), and UpdateItem(Object, IEnumerableSyncId, ItemFieldDictionary, RecoverableErrorReportingContext, ItemFieldDictionary, Boolean). The following code examples provide an implementation for each of these methods:
Synchronizing Two Providers
The following code example shows how source and destination replicas are synchronized. After creating source and destination providers, the code sets SyncOrchestrator options, and synchronizes the replicas.
In this sample, the conflict handling policies for concurrency conflicts and constraint conflicts are left as the default of ApplicationDefined. This means that the application will register for the ItemConflicting and ItemConstraint events and specify an action to resolve conflicts if they occur during synchronization processing. For more information, see Handling Conflicts for Simple Providers. The following code example shows the event handlers that are specified in the constructor of MyFullEnumerationSimpleSyncProvider:
The following code example shows the event handlers setting the conflict resolution actions to Merge:
The following code example shows the MergeConstraintConflict(Object, ConflictVersionInformation, IEnumerableSyncId, ItemFieldDictionary, ItemFieldDictionary, RecoverableErrorReportingContext, ItemFieldDictionary) method that is implemented to respond to a resolution action of Merge for a constraint conflict:
Some applications require data to be filtered, so that only data that meets specific criteria is applied to a destination. For example, a salesperson might require detailed product information only for those products that she sells regularly. Simple providers enable replicas to filter data by implementing a filtering interface and by negotiating filter use.
The following code example uses filter negotiation interfaces to determine whether a specific filter should be used during a synchronization session. Filter negotiation enables a destination provider to specify that the source provider should use one or more filters during change enumeration; the source provider can accept or reject a filter. If a source provider does not support any of the requested filters, the destination provider can choose to receive all the data and do the filtering itself. Sync Framework calls the providers appropriately to negotiate filter usage.
The following code example first specifies a filter option of None. This means that items should be filtered out even if they are already known to the destination. The code example then implements the IsItemInFilterScope(ItemFieldDictionary) method, which filters out items based on one of the item field values. After the filter is defined, the code example implements the UseFilterThisSession method. This enables an application to specify whether filtering should be used on a per-session basis.
Performing Local-Only Deletes
Some synchronization scenarios require the ability to delete an item at a local replica without propagating that delete to other replicas. For example, a server might synchronize with several devices that store information for different salespeople. Each device has limited space, so salespeople delete old completed orders from the device. These kinds of deletes should not be propagated to the server, because the server still requires this data. Simple providers let you specify that the data should only be deleted locally. To control the behavior of deletes on a per-session basis, specify the appropriate option by using SetDeleteMode(SimpleSyncProviderDeleteMode). The following code example specifies that deletes should not be propagated during synchronization.