Поделиться через


Как фильтровать реплики

В этом разделе описано использование управляемого языка для реализации поставщика Sync Framework, представляющего фильтруемые реплики. В фильтруемой реплике хранятся данные только для элементов или базовых единиц, содержащихся в фильтре.

Материал этого раздела предполагает, что читатель владеет основными понятиями языка C# и платформы Microsoft .NET Framework.

В примерах, приведенных в этом разделе, в основном рассматриваются следующие классы платформы Sync Framework и их члены:

Основные сведения о фильтруемых репликах

Фильтруемая реплика хранит данные элементов и базовых единиц для элементов и базовых единиц, имеющихся в фильтре, а также фантомы, то есть метаданные для элементов и базовых единиц, которые были исключены из фильтра. Фильтруемая реплика отслеживает свой фильтр и может также отслеживать другие фильтры. Фильтруемая реплика может согласовывать фильтр с поставщиком источника. В этом случае поставщик источника создает фильтрованный пакет изменений. Если поставщик источника не может создать фильтрованный пакет изменений, фильтруемый поставщик может самостоятельно отфильтровать изменения и применить только те из них, которые включены в фильтр.

В фильтруемом поставщике реализован метод IFilteredReplicaNotifyingChangeApplierTarget для передачи в объект применения изменений сведений об элементах, для которых изменилось отношение к фильтру. Фильтруемый поставщик также обычно реализует интерфейс IRequestFilteredSync для обеспечения возможности согласования фильтра, которым поставщик источника пользуется для перечисления изменений.

Требования построения

Пример

Код примера в этом разделе описывает реализацию фильтруемого поставщика назначения. Фильтруемый поставщик запрашивает фильтруемый пакет изменений, включая фантомы, и применяет отфильтрованные изменения к реплике назначения. Реплика в этом примере является текстовым файлом, в котором хранится контактная информация в виде списка значений с разделителями-запятыми. Синхронизируемыми элементами являются контакты, содержащиеся в этом файле. Фильтр представляет собой строку, согласно которой контакт включается только при обнаружении строки фильтра в поле адреса контакта.

Согласование фильтра

При вызове платформой Sync Framework метода SpecifyFilter реплики назначения запрашивается фильтр, используемый поставщиком источника для перечисления изменений. В этом примере первый фильтр в списке фильтров отслеживается репликой назначения и вызывает исключение при отклонении фильтра поставщиком источника.

public void SpecifyFilter(FilterRequestCallback filterRequest)
{
    // Use the first tracked filter as the filter for sync.
    if (0 < _ContactStore.TrackedFilters.Count)
    {
        _filterForSync = _ContactStore.TrackedFilters[0];
    }

    // The source provider must agree to send a filtered change batch.
    if (!filterRequest(_filterForSync, FilteringType.CurrentItemsAndVersionsForMovedOutItems))
    {
        throw new SyncInvalidOperationException("Filter specified by SpecifyFilter was rejected.");
    }
}

Применение фильтрованных изменений

Поставщик назначения использует объект применения изменений для обработки пакета изменений. Служба хранилища метаданных не поддерживает пользовательские фильтры, поэтому списки локальных версий необходимо обновить для пометки изменений фантомов до отправки списка объекту применения изменений.

public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
    // Use the metadata storage service to get the local versions of changes received from the source provider.
    IEnumerable<ItemChange> localVersions = _ContactStore.ContactReplicaMetadata.GetLocalVersions(sourceChanges);
    // Copy and fix up the local version list to include ghost information.
    List<ItemChange> fixedLocalVersions = new List<ItemChange>();
    ChangeKind fixedChangeKind;
    foreach (ItemChange localVersion in localVersions)
    {
        fixedChangeKind = localVersion.ChangeKind;
        if (localVersion.ChangeKind != ChangeKind.UnknownItem && _ContactStore.IsGhost(localVersion.ItemId))
        {
            fixedChangeKind = ChangeKind.Ghost;
        }
        fixedLocalVersions.Add(new ItemChange(IdFormats, localVersion.ReplicaId, localVersion.ItemId, fixedChangeKind, localVersion.CreationVersion,
            localVersion.ChangeVersion));
    }

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

Объект применения изменений вызывает метод SaveItemChange для сохранения изменений. Фильтруемая реплика обрабатывает действия изменений, влияющих на фантомы.

case SaveChangeAction.CreateGhost:
case SaveChangeAction.UpdateGhost:
{
    try
    {
        _ContactStore.UpdateGhostFromSync(change, _filterKeyMap);
    }
    catch (Exception ex)
    {
        RecoverableErrorData errData = new RecoverableErrorData(ex);
        context.RecordRecoverableErrorForItem(errData);
    }

    break;
}

case SaveChangeAction.MarkItemAsGhost:
{
    try
    {
        // Delete the item from the contact store and update the metadata to indicate it is a ghost.
        _ContactStore.MarkItemAsGhost(change, _filterKeyMap);
    }
    catch (Exception ex)
    {
        RecoverableErrorData errData = new RecoverableErrorData(ex);
        context.RecordRecoverableErrorForItem(errData);
    }

    break;
}

case SaveChangeAction.UnmarkItemAsGhost:
{
    try
    {
        // Create the item in the contact store and update the metadata to indicate the item is not a ghost.
        _ContactStore.UnmarkItemAsGhost(change, (string)context.ChangeData, _filterKeyMap);
    }
    catch (Exception ex)
    {
        RecoverableErrorData errData = new RecoverableErrorData(ex);
        context.RecordRecoverableErrorForItem(errData);
    }

    break;
}

case SaveChangeAction.DeleteGhostAndStoreTombstone:
{
    try
    {
        _ContactStore.DeleteGhostFromSync(change.ItemId, change.ChangeVersion);
    }
    catch (Exception ex)
    {
        RecoverableErrorData errData = new RecoverableErrorData(ex);
        context.RecordRecoverableErrorForItem(errData);
    }

    break;
}
public void UpdateGhostFromSync(ItemChange itemChange, FilterKeyMap providerFilterKeyMap)
{
    // Find the ghost metadata in our list or load it from the metadata store.
    ItemMetadata itemMeta = null;
    if (_ContactGhostMetaList.ContainsKey(itemChange.ItemId))
    {
        itemMeta = _ContactGhostMetaList[itemChange.ItemId];
    }
    else
    {
        itemMeta = _ContactReplicaMetadata.FindItemMetadataById(itemChange.ItemId);
    }
    
    // The ghost does not exist, so create it and add it to the metadata store.
    if (null == itemMeta)
    {
        itemMeta = _ContactReplicaMetadata.CreateItemMetadata(itemChange.ItemId,
            itemChange.CreationVersion);

        InitializeFilterTrackingFields(itemMeta);

        // Create values for all index fields in the metadata store.
        itemMeta.SetCustomField(FirstNameField, itemChange.ItemId.ToString());
        itemMeta.SetCustomField(LastNameField, "0");
        itemMeta.SetCustomField(PhoneNumberField, "0");
    
        _ContactGhostMetaList.Add(itemMeta.GlobalId, itemMeta);
    }

    // Set the version metadata for the change unit by using the metadata storage service.
    itemMeta.ChangeVersion = itemChange.ChangeVersion;

    // Update the filter tracking metadata for filter change metadata sent from the source provider.
    for (int iFilter = 0; iFilter < _trackedFilters.Count; iFilter++)
    {
        // Get filter change metadata from the source provider for this change, if it exists.
        FilterChange filterChange = GetFilterChange(itemChange, iFilter, providerFilterKeyMap);

        // If filter change metadata is present, use it to update the item metadata.
        if (null != filterChange)
        {
            SetIsInFilter(itemMeta, iFilter, filterChange.IsMoveIn);
            SetMoveVersion(itemMeta, iFilter, filterChange.MoveVersion);
        }
    }
}

public bool IsGhost(SyncId itemId)
{
    bool isGhost = false;

    ItemMetadata itemMeta = _ContactReplicaMetadata.FindItemMetadataById(itemId);
    if (null != itemMeta)
    {
        // The item is a ghost if it is not deleted and it does not exist in the contact store.
        isGhost = (!itemMeta.IsDeleted && !_ContactList.ContainsKey(itemId));
    }

    return isGhost;
}

public void MarkItemAsGhost(ItemChange itemChange, FilterKeyMap providerFilterKeyMap)
{
    // Delete the item from the contact store.
    _ContactList.Remove(itemChange.ItemId);

    // Move the item from the active metadata list to the ghost list.
    ItemMetadata ghostMeta = _ContactItemMetaList[itemChange.ItemId];
    _ContactGhostMetaList.Add(itemChange.ItemId, ghostMeta);
    _ContactItemMetaList.Remove(itemChange.ItemId);

    // Update the filter tracking metadata for filter change metadata sent from the source provider.
    for (int iFilter = 0; iFilter < _trackedFilters.Count; iFilter++)
    {
        // Get filter change metadata from the source provider for this change, if it exists.
        FilterChange filterChange = GetFilterChange(itemChange, iFilter, providerFilterKeyMap);

        // If filter change metadata is present, use it to update the item metadata.
        if (null != filterChange)
        {
            SetIsInFilter(ghostMeta, iFilter, filterChange.IsMoveIn);
            SetMoveVersion(ghostMeta, iFilter, filterChange.MoveVersion);
        }
    }
}

public void UnmarkItemAsGhost(ItemChange itemChange, string changeData, FilterKeyMap providerFilterKeyMap)
{
    // Get the metadata for the ghost.
    ItemMetadata itemMeta = null;
    if (_ContactGhostMetaList.ContainsKey(itemChange.ItemId))
    {
        itemMeta = _ContactGhostMetaList[itemChange.ItemId];
    }
    else
    {
        itemMeta = _ContactReplicaMetadata.FindItemMetadataById(itemChange.ItemId);
    }

    if (null == itemMeta)
    {
        throw new SyncInvalidOperationException("UnmarkItemAsGhost received an item but has not metadata for the item.");
    }

    // Create a new contact and add it to the contact store.
    Contact contact = new Contact();
    _ContactList.Add(itemMeta.GlobalId, contact);
    _ContactList[itemChange.ItemId].FromString(changeData);

    // Move the metadata from the ghost list to the active list.
    _ContactItemMetaList.Add(itemMeta.GlobalId, itemMeta);
    _ContactGhostMetaList.Remove(itemMeta.GlobalId);

    // Update the metadata for the item.
    UpdateContactMetadataInternal(itemChange.ItemId, itemChange.ChangeVersion, itemChange, providerFilterKeyMap);
}

// Mark a ghost as deleted in the metadata store.
public void DeleteGhostFromSync(SyncId itemId, SyncVersion changeVersion)
{
    // Find the item in the ghost metadata list or load it from the metadata store.
    ItemMetadata itemMeta = null;
    if (_ContactGhostMetaList.ContainsKey(itemId))
    {
        itemMeta = _ContactGhostMetaList[itemId];
    }
    else
    {
        itemMeta = _ContactReplicaMetadata.FindItemMetadataById(itemId);
    }

    if (null == itemMeta)
    {
        throw new SyncInvalidOperationException("DeleteGhostFromSync received item but has no metadata.");
    }

    //Mark item as deleted.
    itemMeta.MarkAsDeleted(changeVersion);

    // Clear the index name field so it doesn't collide with future items.
    itemMeta.SetCustomField(FirstNameField, itemMeta.GlobalId.ToString());

    // Move the item to the deleted list.
    _ContactDeletedItemMetaList.Add(itemMeta);
    _ContactGhostMetaList.Remove(itemMeta.GlobalId);
}

Перечисление элементов, включенных в фильтр

Фильтруемый поставщик реализует интерфейс IFilteredReplicaNotifyingChangeApplierTarget для передачи в объект применения изменений сведений об элементах, для которых изменилось отношение к фильтру. В примере выполняется перечисление всех элементов в хранилище метаданных и добавление элемента к возвращенному списку, если элемент включен в фильтр реплики и версия перемещения элемента не содержится в указанном базовом наборе знаний.

public IEnumerator<SyncId>  GetNewMoveInItems(SyncKnowledge baseKnowledge)
{
     List<SyncId> newMoveInIdList = new List<SyncId>();
    IEnumerable<ItemMetadata> allItems = _ContactStore.ContactReplicaMetadata.GetAllItems(false);
    SyncKnowledge mappedBaseKnowledge = _ContactStore.ContactReplicaMetadata.GetKnowledge().MapRemoteKnowledgeToLocal(baseKnowledge);
    foreach (ItemMetadata itemMeta in allItems)
    {
        FilterChange filterChange = _ContactStore.GetTrackedFilterMetadata(itemMeta, _filterForSync);
        if (filterChange.IsMoveIn)
        {
            if (!mappedBaseKnowledge.Contains(_ContactStore.ContactReplicaMetadata.ReplicaId, itemMeta.GlobalId, filterChange.MoveVersion))
            {
                newMoveInIdList.Add(itemMeta.GlobalId);
            }
        }
    }
    return newMoveInIdList.GetEnumerator();
}

Следующие шаги

Затем к поставщику необходимо добавить согласование фильтров для поддержки его взаимодействия с поставщиком назначения для определения фильтра, который будет использоваться для перечисления изменений. Дополнительные сведения о согласовании фильтров см. в разделе Как согласовать фильтр.

Рекомендуется также обеспечить для поставщика возможность отслеживания фильтров. Применение реплик, отслеживающих фильтры, позволяет сохранить небольшой размер набора знаний в случае отправки изменений в фильтруемые реплики. Дополнительные сведения о реализации отслеживающего фильтры поставщика см. в разделе Как отслеживать фильтры и перечислять фильтруемые изменения.

См. также

Основные положения

Программирование типовых задач стандартных пользовательских поставщиков
Фильтрация данных синхронизации