如何创建托管同步提供程序

本主题说明如何使用托管语言创建一个 Sync Framework 同步提供程序以同步自定义数据存储区的数据。

本主题假定您基本熟悉 C# 和 Microsoft .NET Framework 概念。

本主题中的示例着重介绍以下 Sync Framework 类和接口:

了解同步提供程序

同步提供程序是在同步期间表示副本的软件组件。它允许该副本将自己的数据与其他副本同步。进行同步时,应用程序首先创建一个 SyncOrchestrator 对象,将该对象与两个 SyncProvider 对象连接,然后启动同步。其中一个提供程序表示源副本。源副本通过 GetChangeBatch 方法为已变更的项提供元数据并通过 IChangeDataRetriever 对象提供项数据。另一个提供程序表示目标副本。目标副本通过 ProcessChangeBatch 方法接收已变更的项的元数据,并使用 Sync Framework 提供的 NotifyingChangeApplier 对象以及自己的 INotifyingChangeApplierTarget 对象将这些变更应用到自己的项存储。

有关同步提供程序的角色的更多信息,请参见实现标准自定义提供程序

生成要求

示例

本主题中的示例代码说明如何实现副本作为源以及目标参与 Sync Framework 同步社区所需的基本类和接口方法。本示例中的副本是一个文本文件,它将联系信息以逗号分隔的值列表形式存储。要同步的项是此文件中包含的联系人。此示例还使用了一个自定义元数据存储区,它是使用元数据存储服务 API 实现的。有关元数据存储服务的信息,请参见 Sync Framework Metadata Storage Service

在下面的示例中,_itemStore 表示同时包含项存储区和元数据存储区的对象。

实现 SyncProvider 和 KnowledgeSyncProvider

提供程序的入口点为 SyncProvider 类。此类将作为其他功能更为强大的提供程序类的基类。本示例使用 KnowledgeSyncProvider 类。

声明 KnowledgeSyncProvider

KnowledgeSyncProvider 添加到类继承列表。

class ContactsProviderXmlMetadataNoChangeUnits : KnowledgeSyncProvider

SyncProviderKnowledgeSyncProvider 方法添加到该类。

IdFormats 属性

SyncOrchestrator 对象上调用 Synchronize 时,Sync Framework 同时在源提供程序和目标提供程序上调用 IdFormatsIdFormats 属性返回提供程序使用的 ID 格式架构。此架构对于源提供程序和目标提供程序必须相同。本示例定义包含 SyncGlobalId 的项 ID 的大小、包含副本绝对路径的副本 ID 的大小以及作为枚举成员的变更单位 ID 的大小。

public override SyncIdFormatGroup IdFormats
{
    get 
    {
        SyncIdFormatGroup FormatGroup = new SyncIdFormatGroup();

        // Item IDs are of SyncGlobalId type, so they are fixed length and contain a ulong prefix plus a Guid.
        FormatGroup.ItemIdFormat.IsVariableLength = false;
        FormatGroup.ItemIdFormat.Length = (ushort)(sizeof(ulong) + Marshal.SizeOf(typeof(Guid)));

        // Replica IDs are the absolute path to the item store, so they are variable length with maximum
        // length equal to the maximum length of a path.
        FormatGroup.ReplicaIdFormat.IsVariableLength = true;
        FormatGroup.ReplicaIdFormat.Length = 260 * sizeof(char);

        return FormatGroup;
    }
}

BeginSession 方法

Sync Framework 在调用任何其他方法前,在源提供程序和目标提供程序上调用 BeginSession。此方法通知提供程序它正在联接同步会话并为该提供程序传递包含会话状态信息的对象。如果提供程序已联接同步会话,则此实现存储会话状态对象或引发 SyncInvalidOperationException

public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
{
    // If this object is already in a session, throw an exception.
    if (null != _sessionContext)
    {
        throw new SyncInvalidOperationException();
    }
    
    _sessionContext = syncSessionContext;
}

GetSyncBatchParameters 方法

Sync Framework 在目标提供程序上调用 GetSyncBatchParameters。此方法检索源提供程序应在变更批中包含的变更数,并获取目标提供程序的当前知识。此实现提取元数据存储区中的知识,然后将批大小设置为 10

public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
{
    // Set a batch size of 10.
    batchSize = 10;

    // Return the current knowledge of the replica.
    knowledge = _itemStore.ContactReplicaMetadata.GetKnowledge();
}

元数据存储区对象通过使用 GetKnowledge 返回副本的当前知识。如果该副本不包含任何知识,则此实现创建新的 SyncKnowledge 对象,并将知识的滴答计数设置为该副本的当前滴答计数。

public override SyncKnowledge GetKnowledge()
{
    // If the replica does not yet contain any knowledge, create a new knowledge object.
    if (null == _knowledge)
    {
        _knowledge = new SyncKnowledge(IdFormats, ReplicaId, _tickCount);            
    }

    // Ensure the tick count of the knowledge is set to the current tick count of the replica.
    _knowledge.SetLocalTickCount(_tickCount);

    return _knowledge;
}

GetChangeBatch 方法

Sync Framework 在源提供程序上调用 GetChangeBatch 时,正式启动同步会话。此方法检索要发送到目标提供程序的一批变更,并返回数据检索器接口。Sync Framework 使用数据检索器接口获取 object,目标提供程序使用该对象检索应用到目标副本的变更的项数据。Sync Framework 重复调用 GetChangeBatch,直到发送了最后一个批。源提供程序通过在变更批对象上调用 SetLastBatch 方法指示某批是最后一个批。此实现将变更枚举任务委托给元数据存储区对象。提供程序对象实现 IChangeDataRetriever;因此在 changeDataRetriever 参数中返回它。

public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
{
    // Return this object as the IChangeDataRetriever object that is called to retrieve item data.
    changeDataRetriever = this;

    // Call the metadata store to get a batch of changes.
    return _itemStore.ContactReplicaMetadata.GetChangeBatch(batchSize, destinationKnowledge);
}

元数据存储区对象通过使用 GetChangeBatch 返回一批变更。此实现将项以按项 ID 排序的 ItemMetadata 对象的列表形式存储在元数据存储区中。如果该变更没有包含在目标知识中,则枚举这些项并将变更添加到变更批对象中的已排序组。枚举元数据存储区中的所有项后,在变更批上调用 SetLastBatch

public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge)
{
    // The destination knowledge must be converted to be compatible with the source replica
    // before it can be used.
    SyncKnowledge mappedDestKnowledge = _knowledge.MapRemoteKnowledgeToLocal(destinationKnowledge);

    // Create a new change batch, initialized by using the current knowledge of the source replica
    // and a new ForgottenKnowledge object.
    ChangeBatch changeBatch = new ChangeBatch(IdFormats, GetKnowledge(), new ForgottenKnowledge());

    // Start a group of changes in the change batch. The group is ordered by item ID.
    // _getChangeBatchCurrent is 0 the first time GetChangeBatch is called, and is used to track the
    // position in the metadata store for subsequent calls to GetChangeBatch.
    changeBatch.BeginOrderedGroup(_items.Values[_getChangeBatchCurrent].GlobalId);
    
    // itemsAdded is incremented each time a change is added to the change batch. When itemsAdded
    // is greater than the requested batch size, enumeration stops and the change batch is returned.
    int itemsAdded = 0;
    
    ItemMetadata itemMeta;

    // Enumerate items and add a change to the change batch if it is not contained in the 
    // destination knowledge.
    // _items is a SortedList that contains ItemMetadata objects that are ordered by item ID.
    for (; itemsAdded <= batchSize && _getChangeBatchCurrent < _items.Count; _getChangeBatchCurrent++)
    {
        itemMeta = _items.Values[_getChangeBatchCurrent];
        ChangeKind kind = (itemMeta.IsDeleted) ? ChangeKind.Deleted : ChangeKind.Update;
        ItemChange change = new ItemChange(IdFormats, ReplicaId, itemMeta.GlobalId, kind, itemMeta.CreationVersion, 
            itemMeta.ChangeVersion);

        // If the change is not contained in the destination knowledge, add it to the change batch.
        if (!mappedDestKnowledge.Contains(change))
        {
            changeBatch.AddChange(change);
            itemsAdded++;
        }
    }

    // End the group of changes in the change batch. Pass the current source knowledge.
    changeBatch.EndOrderedGroup(_items.Values[_getChangeBatchCurrent - 1].GlobalId, _knowledge);

    // When all items in the metadata store have been enumerated, set this batch as the
    // last batch.
    if (_getChangeBatchCurrent == _items.Count)
    {
        changeBatch.SetLastBatch();
    }

    return changeBatch;
}

ProcessChangeBatch 方法

Sync Framework 通过调用 GetChangeBatch 方法从源提供程序获取一批变更后,Sync Framework 在目标提供程序上调用 ProcessChangeBatch。此方法将这些变更应用到目标副本。对于从 GetChangeBatch 检索的每个变更批,在源提供程序上调用一次此方法。此实现使用元数据存储区对象来获取源提供程序中项的本地版本信息。然后它创建由 Sync Framework 实现的 NotifyingChangeApplier 对象并调用其 ApplyChanges 方法。

public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
    // Use the metadata store to get the local versions of changes received from the source provider.
    IEnumerable<ItemChange> destVersions = _itemStore.ContactReplicaMetadata.GetLocalVersions(sourceChanges);

    // Use a NotifyingChangeApplier object to process the changes. Note that this object is passed as the INotifyingChangeApplierTarget
    // object that will be called to apply changes to the item store.
    NotifyingChangeApplier changeApplier = new NotifyingChangeApplier(IdFormats);
    changeApplier.ApplyChanges(resolutionPolicy, sourceChanges, (IChangeDataRetriever)changeDataRetriever, destVersions,
        _itemStore.ContactReplicaMetadata.GetKnowledge(), _itemStore.ContactReplicaMetadata.GetForgottenKnowledge(), 
        this, _sessionContext, syncCallbacks);
}

元数据存储区对象使用 GetLocalVersions 从源提供程序返回项的本地版本信息。此实现枚举源提供程序的变更批中发送的变更。如果项存在于目标元数据中,将其版本信息添加到包含该版本信息的变更列表中。如果目标元数据中不存在该项,则在本地版本列表中将其标记为未知项。

public override IEnumerable<ItemChange> GetLocalVersions(ChangeBatch sourceChanges)
{
    List<ItemChange> localVersions = new List<ItemChange>();

    // Enumerate the source changes and retrieve the destination version for each source change. 
    foreach (ItemChange srcItem in sourceChanges)
    {
        ItemChange localVer;

        // When the source item exists in the destination metadata store, retrieve the destination version of the item.
        if (_items.ContainsKey(srcItem.ItemId))
        {
            XmlItemMetadata localMeta = _items[srcItem.ItemId];
            ChangeKind kind = (localMeta.IsDeleted) ? ChangeKind.Deleted : ChangeKind.Update;
            localVer = new ItemChange(IdFormats, ReplicaId, srcItem.ItemId, kind, localMeta.CreationVersion, localMeta.ChangeVersion);
        }
        // When the source item does not exist in the destination metadata store, create a new change with unknown
        // version information.
        else
        {
            localVer = new ItemChange(IdFormats, ReplicaId, srcItem.ItemId, ChangeKind.UnknownItem, SyncVersion.UnknownVersion, SyncVersion.UnknownVersion);
        }

        localVersions.Add(localVer);
    }

    return localVersions;
}

EndSession 方法

在源提供程序发送最后一批变更且目标提供程序将这些变更应用到自己的数据存储区后,Sync Framework 在源提供程序和目标提供程序上调用 EndSession。此方法通知提供程序它正在退出同步会话并将释放与该会话关联的所有资源。如果提供程序以前没有联接同步会话,此实现将释放在 BeginSession 调用中存储的会话状态对象或引发 SyncInvalidOperationException

public override void EndSession(SyncSessionContext syncSessionContext)
{
    // If this object is not in a session, throw an exception.
    if (null == _sessionContext)
    {
        throw new SyncInvalidOperationException();            
    }

    _sessionContext = null;
}

未实现的方法

以下方法不是必需的,因为本示例从不移去元数据存储区中标记为“已删除”的项。这些方法可以引发 NotImplementedException

实现 INotifyingChangeApplierTarget

目标提供程序调用 ApplyChanges 方法(通常位于 ProcessChangeBatch 方法中)时,将此接口提供给 Sync Framework。INotifyingChangeApplierTarget 包含在应用变更期间调用的方法。仅在目标提供程序上调用这些方法。

声明 INotifyingChangeApplierTarget

INotifyingChangeApplierTarget 添加到您的类继承列表。

class ContactsProviderXmlMetadataNoChangeUnits : KnowledgeSyncProvider
    , INotifyingChangeApplierTarget

INotifyingChangeApplierTarget 方法添加到您的类。

IdFormats 属性

Sync Framework 调用 IdFormats 来检索提供程序的 ID 格式架构。本示例使用相同的类来实现 KnowledgeSyncProviderINotifyingChangeApplierTarget。因此,此实现与上面 KnowledgeSyncProviderIdFormats 属性的实现相同。

GetNextTickCount

Sync Framework 调用 GetNextTickCount 以递增和检索副本的滴答计数。此实现调用元数据存储区对象的 GetNextTickCount 方法。

public ulong GetNextTickCount()
{
    return _itemStore.ContactReplicaMetadata.GetNextTickCount();
}

GetNextTickCount 的元数据存储区实现递增副本的滴答计数并返回该计数。

public override ulong GetNextTickCount()
{
    return ++_tickCount;
}

SaveItemChange

在应用变更期间,Sync Framework 为要应用到目标副本的每个变更调用 SaveItemChange。此实现对于收到的每个变更类型更新项存储区和元数据存储区。创建或更新项时,在 context 参数的 ChangeData 属性中接收项数据。ChangeData 属性包含从源提供程序的 LoadChangeData 方法返回的 object。应用变更后,存储更新的目标知识。

public void SaveItemChange(SaveChangeAction saveChangeAction, ItemChange change, SaveChangeContext context)
{
    switch (saveChangeAction)
    {
        // Update the item store and metadata store when an item is created or updated.
        case SaveChangeAction.Create:
        case SaveChangeAction.UpdateVersionAndData:
        {
            try
            {
                _itemStore.UpdateContactFromSync(change, (string)context.ChangeData);
            }
            catch (Exception ex)
            {
                RecoverableErrorData errData = new RecoverableErrorData(ex);
                context.RecordRecoverableErrorForItem(errData);
            }
            break;
        }

        // Update only the version of this item in the metadata store.
        case SaveChangeAction.UpdateVersionOnly:
        {
            try
            {
                _itemStore.UpdateContactVersion(change.ItemId, change.ChangeVersion);
            }
            catch (Exception ex)
            {
                RecoverableErrorData errData = new RecoverableErrorData(ex);
                context.RecordRecoverableErrorForItem(errData);
            }
            break;
        }

        // Delete the item from the item store and store a tombstone for it in the metadata store.
        case SaveChangeAction.DeleteAndStoreTombstone:
        {
            try
            {
                _itemStore.DeleteContactFromSync(change.ItemId, change.ChangeVersion);
            }
            catch (Exception ex)
            {
                RecoverableErrorData errData = new RecoverableErrorData(ex);
                context.RecordRecoverableErrorForItem(errData);
            }

            break;
        }

        // Neither merging of data nor removing tombstones is supported.
        case SaveChangeAction.UpdateVersionAndMergeData:
        case SaveChangeAction.DeleteAndRemoveTombstone:
        {
            throw new NotImplementedException();
        }

        default:
        {
            throw new ArgumentOutOfRangeException();
        }
    }

    // Save the knowledge in the metadata store as each change is applied. Saving knowledge as each change is applied is 
    // not required. It is more robust than saving the knowledge only after each change batch, because if synchronization is interrupted 
    // before the end of a change batch, the knowledge will still reflect all of the changes applied. However, it is less efficient because 
    // knowledge must be stored more frequently.
    SyncKnowledge knowledge;
    ForgottenKnowledge forgottenKnowledge;
    context.GetUpdatedDestinationKnowledge(out knowledge, out forgottenKnowledge);
    _itemStore.ContactReplicaMetadata.SetKnowledge(knowledge);
}

在下面的示例中,_ContactItemMetaList 包含 ItemMetadata 对象。

联系人存储区的 UpdateContactFromSync 方法更新指定的联系人。如果联系人不存在,则创建新联系人并将其添加到联系人存储区。还更新元数据存储区以反映联系人存储区的变更。

public void UpdateContactFromSync(ItemChange itemChange, string changeData)
{
    // If the item does not exist, create a new contact and add it to the contact and metadata store.
    if (!_ContactList.ContainsKey(itemChange.ItemId))
    {
        Contact contact = new Contact();
        ItemMetadata newItemMeta = _ContactReplicaMetadata.CreateItemMetadata(itemChange.ItemId,
            itemChange.CreationVersion);

        _ContactList.Add(newItemMeta.GlobalId, contact);
        _ContactItemMetaList.Add(newItemMeta.GlobalId, newItemMeta);
    }

    // Update the specified contact in the contact store. changeData is the contact data returned by the
    // IChangeDataRetriever.LoadChangeData method of the source provider.
    _ContactList[itemChange.ItemId].FromString(changeData);

    // Get the metadata for the specified item.
    ItemMetadata itemMeta = _ContactItemMetaList[itemChange.ItemId];

    // Update the index fields for the item. This implementation defines an index that uniquely identifies each contact.
    // The index consists of the first name, last name, and phone number of the contact.
    itemMeta.SetCustomField(FirstNameField, _ContactList[itemChange.ItemId].FirstName);
    itemMeta.SetCustomField(LastNameField, _ContactList[itemChange.ItemId].LastName);
    itemMeta.SetCustomField(PhoneNumberField, _ContactList[itemChange.ItemId].PhoneNumber);

    // Update the version for the change.
    itemMeta.ChangeVersion = itemChange.ChangeVersion;
}

联系人存储区的 UpdateContactVersion 方法更新指定项的版本元数据。

public void UpdateContactVersion(SyncId itemId, SyncVersion itemVersion)
{
    // Update the version metadata for the specified item.
    _ContactItemMetaList[itemId].ChangeVersion = itemVersion;
}

联系人存储区的 DeleteContactFromSync 方法从联系人存储区删除项并在元数据存储区中将其标记为“已删除”。

public void DeleteContactFromSync(SyncId itemId, SyncVersion version)
{
    if (_ContactList.ContainsKey(itemId))
    {
        // Remove the item from the contact store.
        _ContactList.Remove(itemId);

        // Mark the item as deleted in the metadata store.
        ItemMetadata itemMeta = _ContactItemMetaList[itemId];
        itemMeta.MarkAsDeleted(version);

        // Change the first index field so the index fields don't collide with future items.
        itemMeta.SetCustomField(FirstNameField, itemId.ToString());

        // Move the metadata for the deleted item to a separate list. 
        // The deleted item metadata must be kept so that it can be committed when
        // SaveChanges is called.
        _ContactDeletedItemMetaList.Add(itemMeta);
        _ContactItemMetaList.Remove(itemId);
    }
    else 
    {
        // An item marked as deleted has been received as part of synchronization, but it does not exist in
        // the item store. Create a tombstone for it in the metadata store.
        ItemMetadata itemMeta = _ContactReplicaMetadata.CreateItemMetadata(itemId, version);
        itemMeta.MarkAsDeleted(version);

        // Clear the index fields so they don't collide with future items.
        itemMeta.SetCustomField(FirstNameField, itemId.ToString());

        _ContactDeletedItemMetaList.Add(itemMeta);
    }
}

StoreKnowledgeForScope

处理每个变更批后,Sync Framework 调用 StoreKnowledgeForScope 以便目标提供程序可以保存包含新变更的知识。此实现将知识对象保存到元数据存储区并覆盖先前已有的知识。将在处理变更批期间对联系人存储区和元数据存储区所做的变更提交给磁盘上的文件。

public void StoreKnowledgeForScope(SyncKnowledge knowledge, ForgottenKnowledge forgottenKnowledge)
{
    _itemStore.ContactReplicaMetadata.SetKnowledge(knowledge);

    // Commit changes made to the in-memory item store to the file on disk.
    _itemStore.SaveContactChanges();

    // Commit changes made to the in-memory metadata store to the file on disk.
    _itemStore.SaveMetadataChanges();
}

联系人存储区的 SaveMetadataChanges 方法将元数据存储区的变更提交给磁盘上的文件。

public void SaveMetadataChanges()
{
    // A transaction is required for saving changes to the metadata store.
    _ContactMetadataStore.BeginTransaction(IsolationLevel.ReadCommitted);

    // Enumerate the deleted items list.
    if (null != _ContactDeletedItemMetaList)
    {
        foreach (ItemMetadata contactMeta in _ContactDeletedItemMetaList)
        {
            // Save the deleted item metadata to the metadata store.
            _ContactReplicaMetadata.SaveItemMetadata(contactMeta);
        }
    }

    // Save renamed items first to avoid collisions in the metadata store.
    foreach (SyncId itemId in _ContactRenameList)
    {
        _ContactReplicaMetadata.SaveItemMetadata(_ContactItemMetaList[itemId]);            
    }

    // Enumerate the active contacts.
    for (int iCon = 0; iCon < _ContactItemMetaList.Count; iCon++)
    { 
        // Save the item metadata to the metadata store.
        _ContactReplicaMetadata.SaveItemMetadata(_ContactItemMetaList.Values[iCon]);
    }

    // Save the replica metadata to the metadata store.
    _ContactReplicaMetadata.SaveReplicaMetadata();

    // Commit the metadata store transaction.
    _ContactMetadataStore.CommitTransaction();
}

未实现的方法

以下方法对于基本同步方案不是必需的,可以引发 NotImplementedException

实现 IChangeDataRetriever

源提供程序将 IChangeDataRetriever 返回给 Sync Framework,以响应 GetChangeBatch 调用。通过 ProcessChangeBatch 调用将 IChangeDataRetriever 发送给目标提供程序,在此调用中一般将 IsynchronousDataRetriever 传递给变更应用方的 ApplyChanges 方法。变更应用方然后调用 LoadChangeData 以获取表示项数据的 object。变更应用方将此接口传递给目标提供程序的 SaveItemChangeSaveChangeWithChangeUnits 方法。目标提供程序使用此 object 检索新项或已变更项的项数据并将项数据应用到目标副本。

声明 IChangeDataRetriever

IChangeDataRetriever 添加到类继承列表。

class ContactsProviderXmlMetadataNoChangeUnits : KnowledgeSyncProvider
    , INotifyingChangeApplierTarget
    , IChangeDataRetriever

IChangeDataRetriever 方法添加到该类。

IdFormats 属性

Sync Framework 调用 IdFormats 来检索提供程序的 ID 格式架构。本示例使用相同的类来实现 KnowledgeSyncProviderIChangeDataRetriever。因此,此实现与上面 KnowledgeSyncProviderIdFormats 属性的实现相同。

LoadChangeData 方法

在应用变更期间,Sync Framework 调用 LoadChangeData 以获取目标提供程序可用于检索项数据的 object。此实现返回序列化为字符串形式的联系人数据。

public object LoadChangeData(LoadChangeContext loadChangeContext)
{
    // Return the specified contact serialized as a string.
    return _itemStore.ContactList[loadChangeContext.ItemChange.ItemId].ToString();
}

后续步骤

接下来,您可能要创建可以承载同步会话的应用程序并将其与该提供程序连接。有关如何执行此操作的信息,请参见如何创建非托管同步应用程序

您还可以增强提供程序以筛选同步的项或变更单位。有关筛选的更多信息,请参见筛选同步数据

您还可以增强提供程序以处理变更单位。有关变更单位的更多信息,请参见同步变更单位

您还可能要创建自定义元数据存储。有关如何处理同步元数据的更多信息,请参见管理标准提供程序的元数据

请参阅

参考

SyncProvider
KnowledgeSyncProvider
INotifyingChangeApplierTarget
IChangeDataRetriever
NotifyingChangeApplier

概念

实现标准自定义提供程序
Sync Framework 核心组件