How to: Track Filters and Enumerate Filtered Changes

This topic describes how to use a managed language to track filters that are used in a Sync Framework synchronization community, and how to enumerate a filtered change batch.

This topic assumes a basic familiarity with C# and Microsoft .NET Framework concepts.

The examples in this topic focus on the following Sync Framework classes and members:

Understanding Filter Tracking

It is recommended that all replicas in a synchronization community track the filters that are used in the community. When a filtered replica receives a filtered change enumeration from a filter-tracking replica, the knowledge of the filtered replica is kept small. When a filtered replica receives a filtered change enumeration from a replica that does not track the filter, the size of the knowledge grows in proportion to the number of changes sent.

A filter-tracking provider implements the IFilterTrackingProvider interface. Sync Framework uses this interface to mediate the negotiation of which filters are tracked by both the source replica and the destination replica.

The source provider sends filter metadata for each mutually tracked filter during synchronization. A filter-tracking provider typically also implements the ISupportFilteredSync interface and is able to enumerate changes that are filtered by any of the tracked filters.

The destination provider implements the IFilterTrackingNotifyingChangeApplierTarget interface, which the change applier uses to obtain and update the filter key map and filter forgotten knowledge of tracked filters. The destination provider also updates the filter change metadata of items and change units that are sent to the SaveItemChange or SaveChangeWithChangeUnits method.

Build Requirements

Example

The example code in this topic shows how to implement a filter-tracking provider that sends filter change metadata and can enumerate a filtered change batch when acting as the source provider, and that applies filter change metadata when acting as the destination provider. The replica in this example is a text file that stores contact information as a list of comma-separated values. The items to synchronize are the contacts that are contained in this file. A filter is a string that causes a contact to be included only if the filter string is found in the address field of the contact.

Negotiating Tracked Filters

To negotiate the tracked filters, Sync Framework calls SpecifyTrackedFilters on the destination provider. This example enumerates the list of filters tracked by the destination replica, sends each filter to the source provider, and adds the filter to the list of mutually tracked filters only when the source provider also tracks the filter.

public void SpecifyTrackedFilters(RequestTrackedFilterCallback filterTrackingRequestCallback)
{
    foreach (AddressFilter filter in _ContactStore.TrackedFilters)
    {
        if (filterTrackingRequestCallback(filter))
        {
            // Add the filter to the list of mutually tracked filters only when the 
            // source provider also tracks the filter.
            _filterKeyMap.AddFilter(filter);
        }
    }
}

For each filter that is enumerated by the destination provider, Sync Framework calls the TryAddTrackedFilter method of the source provider. This example checks whether the specified filter is tracked by the source replica and, if it is, adds it to the list of mutually tracked filters.

public bool TryAddTrackedFilter(ISyncFilter filter)
{
    bool isTracked = false;
    foreach (AddressFilter addressFilter in _ContactStore.TrackedFilters)
    {
        // Add the filter to the list of mutually tracked filters only when it
        // is identical to one of the filters of this replica.
        if (addressFilter.IsIdentical(filter))
        {
            _filterKeyMap.AddFilter(addressFilter);
            isTracked = true;
            break;
        }
    }
    return isTracked;
}

Negotiating the Filter Used for Synchronization

When the destination replica is a filtered replica, it typically requests the filter that the source provider uses to enumerate changes. The source provider receives this request through the TryAddFilter method. This example checks whether the requested filter is a mutually tracked filter and, if it is, saves it to use when enumerating changes.

public bool TryAddFilter(object filter, FilteringType filteringType)
{
    _filterForSync = null;

    // The filter must be tracked by both replicas.
    for (int filterKey = 0; filterKey < _filterKeyMap.Count; filterKey++)
    {
        if (_filterKeyMap[filterKey].IsIdentical((ISyncFilter)filter))
        {
            _filterForSync = (AddressFilter)_filterKeyMap[filterKey];
            _filteringType = filteringType;
            break;
        }
    }

    return (null != _filterForSync);
}

Sending Filter Metadata and Filtered Changes

The source provider sends filter metadata when it sends a batch of changes. This example can send either a filtered change batch or an unfiltered change batch, depending on whether a filter is used for synchronization. Each change that is included in the change batch has filter change metadata attached to it when the item has changed in relation to a tracked filter. Filter forgotten knowledge for each tracked filter is also sent. When a filter is used for synchronization, an item is sent when it is in the filter, and an item that has moved out of the filter is marked as a ghost.

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;

    // The metadata storage service does not support filter tracking, so enumerate changes manually.
    ChangeBatch changeBatch;
    if (null == _filterForSync)
    {
        // No filter was specified for synchronization, so produce an unfiltered change batch.
        changeBatch = new ChangeBatch(IdFormats, destinationKnowledge, _ContactStore.ContactReplicaMetadata.GetForgottenKnowledge());
    }
    else
    {
        // A filter was specified for synchronization, so produce a filtered change batch.
        CustomFilterInfo filterInfo = new CustomFilterInfo(IdFormats, _filterForSync);
        changeBatch = new ChangeBatch(IdFormats, destinationKnowledge, _filterForSync.FilterForgottenKnowledge, filterInfo);
    }

    // If the destination replica tracks filters that are tracked by the source replica, 
    // set the filter key map of the change batch.
    if (0 < FilterKeyMap.Count)
    {
        // Add the filter key map to the change batch before any groups are started.
        changeBatch.FilterKeyMap = FilterKeyMap;
    }

    // Get all the items from the metadata store.
    IEnumerable<ItemMetadata> allItems = _ContactStore.ContactReplicaMetadata.GetAllItems(true);
    
    // Convert the destination knowledge for use with local versions.
    SyncKnowledge mappedDestKnowledge = _ContactStore.ContactReplicaMetadata.GetKnowledge().MapRemoteKnowledgeToLocal(destinationKnowledge);

    // Build the list of items in the change batch.
    List<ItemChange> itemChanges = new List<ItemChange>((int)batchSize);
    uint cItemsInBatch = 0;
    SyncId replicaId = _ContactStore.ContactReplicaMetadata.ReplicaId;
    foreach (ItemMetadata itemMeta in allItems)
    {
        // Process all items if this is an unfiltered enumeration.
        // Otherwise, only process an item that has been in the filter. An item has been in the filter if
        // it is currently in the filter or if its move version in relation to the filter is a value
        // other than (0,0).
        if (null == _filterForSync || _ContactStore.HasBeenInFilter(itemMeta, _filterForSync))
        {
            // If a change is not contained in the destination knowledge, add it to the change batch.
            if (!mappedDestKnowledge.Contains(replicaId, itemMeta.GlobalId, itemMeta.ChangeVersion))
            {
                ChangeKind kind;
                if (itemMeta.IsDeleted)
                {
                    kind = ChangeKind.Deleted;
                }
                // An item that has been in the filter but is not currently in the filter has
                // recently moved out, so it must be marked as a ghost.
                else if (null != _filterForSync 
                    && !_filterForSync.IsInFilter(_ContactStore.ContactList[itemMeta.GlobalId]))
                {
                    kind = ChangeKind.Ghost;
                }
                else
                {
                    kind = ChangeKind.Update;
                }

                ItemChange itemChange = new ItemChange(IdFormats, _ContactStore.ContactReplicaMetadata.ReplicaId,
                    itemMeta.GlobalId, kind, itemMeta.CreationVersion, itemMeta.ChangeVersion);

                // Pass along any filter information for filters tracked by both the source and destination replicas.
                _ContactStore.AddFilterChanges(_filterKeyMap, itemMeta, mappedDestKnowledge, itemChange);

                // Add the item to the change list. Include ghosts only if the destination requested ghosts.
                if (kind != ChangeKind.Ghost || (kind == ChangeKind.Ghost && FilteringType.CurrentItemsAndVersionsForMovedOutItems == _filteringType))
                {
                    itemChanges.Add(itemChange);
                }
                cItemsInBatch++;
            }
        }

        if (batchSize <= cItemsInBatch)
        {
            break;
        }
    }

    // Add the list of items to the change batch object.
    if (0 < itemChanges.Count)
    {
        changeBatch.BeginOrderedGroup(itemChanges[0].ItemId);

        // Set the filter forgotten knowledge for each filter that the destination has requested.
        for (int iFilter = 0; iFilter < FilterKeyMap.Count; iFilter++)
        {
            AddressFilter addressFilter = (AddressFilter)FilterKeyMap[iFilter];
            changeBatch.SetFilterForgottenKnowledge((uint)iFilter, addressFilter.FilterForgottenKnowledge);
        }

        changeBatch.AddChanges(itemChanges);

        // End the group of changes in the change batch. Pass the current source knowledge.
        changeBatch.EndOrderedGroup(itemChanges[itemChanges.Count - 1].ItemId, _ContactStore.ContactReplicaMetadata.GetKnowledge());

        // If all items were enumerated before the batch was filled, then this is the last batch.
        if (cItemsInBatch < batchSize)
        {
            changeBatch.SetLastBatch();
        }
    }
    else 
    {
        throw new InvalidOperationException("GetChangeBatch called but there are no new changes to enumerate.");
    }

    return changeBatch;
}

Filter change metadata is examined for each mutually tracked filter. Filter change metadata is added to a change when the move version of the change is not contained in the destination knowledge.

public void AddFilterChanges(FilterKeyMap filterKeyMap, ItemMetadata itemMeta, SyncKnowledge destKnowledge,
    ItemChange itemChange)
{
    for (int filterKey = 0; filterKey < filterKeyMap.Count; filterKey++)
    {
        // Find the filter in the list of all filters tracked by this replica.
        int iFilter = 0;
        for (; iFilter < _trackedFilters.Count; iFilter++)
        {
            if (filterKeyMap[filterKey].IsIdentical(_trackedFilters[iFilter]))
            {
                break;
            }
        }

        // Get the filter information for the item and add it to the ItemChange object.
        SyncVersion moveVersion = GetMoveVersion(itemMeta, iFilter);

        // Only return a filter change if the destination knowledge does not contain the version of the 
        // last move that occurred in relation to the specified filter.
        FilterChange filterChange = null;
        if (!destKnowledge.Contains(ContactReplicaMetadata.ReplicaId, itemMeta.GlobalId, moveVersion))
        {
            filterChange = new FilterChange(GetIsInFilter(itemMeta, iFilter), moveVersion);
            itemChange.AddFilterChange((uint)filterKey, filterChange);
        }
    }
}

Applying Filter Metadata

Changes are applied by using a change applier, which calls the SaveItemChange method of the destination provider. When an item is created or updated, this example creates or updates the data in the contact store, updates the synchronization metadata, and updates the filter tracking metadata. Filter tracking metadata is updated for all filters that are tracked by the destination provider, not just for mutually tracked filters. When filter metadata is sent by the source provider, it is used, otherwise the change is compared to each tracked filter and the appropriate filter change metadata is set.

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

        InitializeFilterTrackingFields(itemMeta);

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

    _ContactList[itemChange.ItemId].FromString(changeData);

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

private void UpdateContactMetadataInternal(SyncId itemId, SyncVersion version, ItemChange itemChange, FilterKeyMap providerFilterKeyMap)
{
    ItemMetadata itemMeta = _ContactItemMetaList[itemId];

    // Set the value of all index fields in the metadata store.
    itemMeta.SetCustomField(FirstNameField, _ContactList[itemId].FirstName);
    itemMeta.SetCustomField(LastNameField, _ContactList[itemId].LastName);
    itemMeta.SetCustomField(PhoneNumberField, _ContactList[itemId].PhoneNumber);

    // Update the version metadata for the change unit.
    itemMeta.ChangeVersion = version;

    // Update the filter tracking metadata both for filter change metadata sent from the source provider and for
    // any other filters tracked by this replica.
    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);
        }
        // Otherwise, update the item metadata for other tracked filters.
        else
        {
            UpdateFilterTrackingMetadata(itemMeta, iFilter, version);
        }
    }
}

// An item has been created or has changed, so update the filter tracking metadata for the item.
void UpdateFilterTrackingMetadata(ItemMetadata itemMeta, int iFilter, SyncVersion moveVersion)
{
    // Determine whether the item is in the filter.
    Contact contact = _ContactList[itemMeta.GlobalId];
    bool isInFilter = _trackedFilters[iFilter].IsInFilter(contact);

    // Determine whether the item was in the filter.
    bool wasInFilter = GetIsInFilter(itemMeta, iFilter);

    // If the filter membership has changed, update the filter tracking metadata.
    if (isInFilter != wasInFilter)
    {
        SetIsInFilter(itemMeta, iFilter, isInFilter);
        SetMoveVersion(itemMeta, iFilter, moveVersion);
    }
}

The change applier also calls the methods of the IFilterTrackingNotifyingChangeApplierTarget interface of the destination provider to obtain and save filter tracking metadata. This example returns the requested objects and saves the specified metadata.

private FilterKeyMap _filterKeyMap;

public FilterKeyMap FilterKeyMap
{
    get 
    {
        return _filterKeyMap;
    }
}

public ForgottenKnowledge GetFilterForgottenKnowledge(uint filterIndex)
{
    if (filterIndex < _filterKeyMap.Count)
    {
        return ((AddressFilter)_filterKeyMap[(int)filterIndex]).FilterForgottenKnowledge;
    }
    else
    {
        throw new ArgumentOutOfRangeException("GetFilterForgottenKnowledge received and out-of-range index.");
    }
}

public void SaveKnowledgeWithFilterForgottenKnowledge(SyncKnowledge syncKnowledge, ForgottenKnowledge forgottenKnowledge, ForgottenKnowledge[] filterForgottenKnowledge)
{
    // First update the list of filter forgotten knowledge objects.
    for (int iFilter = 0; iFilter < filterForgottenKnowledge.Length; iFilter++)
    {
        ((AddressFilter)_filterKeyMap[iFilter]).FilterForgottenKnowledge = filterForgottenKnowledge[iFilter];
    }

    // Update the list of filters that are stored in the custom replica metadata.
    AddressFilter.StoreFiltersInReplicaMetadata(_ContactStore.ContactReplicaMetadata, _ContactStore.TrackedFilters);

    // Store the remaining knowledge objects.
    StoreKnowledgeForScope(syncKnowledge, forgottenKnowledge);
}

Storing Filter Metadata

The filters that are tracked by a replica must be stored in the replica, along with the filter forgotten knowledge of each tracked filter. This example uses the metadata storage service to store metadata. The metadata storage service does not support custom filters, so tracked filters are serialized to a byte stream and stored in the custom replica metadata field of the metadata store.

class AddressFilter : ISyncFilter, ISyncFilterDeserializer
{
    // For deserialization.
    public AddressFilter()
    {
        _filter = null;        
    }

    // A filter is a string that is compared against the Address field of a contact.
    public AddressFilter(string filter)
    {
        _filter = filter;
    }

    public string Filter
    {
        get
        {
            return _filter;
        }
    }

    // A contact is in the filter when the filter string is contained in the Address field of the contact.
    public bool IsInFilter(Contact contact)
    {
        return contact.Address.Contains(_filter);
    }

    private string _filter;

    public ForgottenKnowledge FilterForgottenKnowledge
    {
        get 
        {
            return _filterForgottenKnowledge;
        }

        set
        {
            _filterForgottenKnowledge = value;
        }
    }

    private ForgottenKnowledge _filterForgottenKnowledge;

    #region ISyncFilter Members

    // Two filters are identical when their filter strings are equal.
    public bool IsIdentical(ISyncFilter otherFilter)
    {
        return _filter.Equals(((AddressFilter)otherFilter).Filter);
    }

    public byte[] Serialize()
    {
        MemoryStream memStream = new MemoryStream();
        BinaryWriter biWriter = new BinaryWriter(memStream, Encoding.Unicode);

        SerializeToBinaryWriter(biWriter);

        return memStream.GetBuffer();
    }

    private void SerializeToBinaryWriter(BinaryWriter biWriter)
    {
        bool hasFilterForgottenKnowledge = (null != _filterForgottenKnowledge);

        biWriter.Write(hasFilterForgottenKnowledge);

        biWriter.Write(_filter);

        if (null != _filterForgottenKnowledge)
        {
            byte[] serializedForgottenKnowledge = _filterForgottenKnowledge.Serialize();
            biWriter.Write(serializedForgottenKnowledge.Length);
            biWriter.Write(serializedForgottenKnowledge);
        }
    }

    #endregion

    #region ISyncFilterDeserializer Members

    public ISyncFilter Deserialize(byte[] data)
    {
        MemoryStream memStream = new MemoryStream(data, 0, data.Length, false, true);
        BinaryReader biReader = new BinaryReader(memStream, Encoding.Unicode);

        DeserializeFromBinaryReader(biReader, memStream);

        return this;
    }

    private void DeserializeFromBinaryReader(BinaryReader biReader, MemoryStream memStream)
    {
        bool hasFilterForgottenKnowledge = biReader.ReadBoolean();

        _filter = biReader.ReadString();

        if (hasFilterForgottenKnowledge)
        {
            int cbForgottenKnowledge = biReader.ReadInt32();
            byte[] rawBuffer = biReader.ReadBytes(cbForgottenKnowledge);
            _filterForgottenKnowledge = ForgottenKnowledge.Deserialize(ContactStore.ContactIdFormatGroup,
                rawBuffer);
        }
    }

    #endregion

    // This implementation uses the metadata storage service to store metadata.
    // The metadata storage service does not support custom filters, so store the filters 
    // that are tracked by a replica in the custom replica metadata field
    // of the metadata store.
    public static void StoreFiltersInReplicaMetadata(ReplicaMetadata repMeta, List<AddressFilter> filters)
    {
        MemoryStream memStream = new MemoryStream();
        BinaryWriter biWriter = new BinaryWriter(memStream, Encoding.Unicode);

        biWriter.Write(filters.Count);

        foreach (AddressFilter filter in filters)
        {
            filter.SerializeToBinaryWriter(biWriter);
        }

        repMeta.CustomReplicaMetadata = memStream.GetBuffer();
    }

    public static List<AddressFilter> ReadFiltersFromReplicaMetadata(ReplicaMetadata repMeta)
    {
        MemoryStream memStream = new MemoryStream(repMeta.CustomReplicaMetadata, 0, repMeta.CustomReplicaMetadata.Length, false, true);
        BinaryReader biReader = new BinaryReader(memStream, Encoding.Unicode);

        int cFilters = biReader.ReadInt32();
        List<AddressFilter> filters = new List<AddressFilter>(cFilters);
        AddressFilter newFilter;
        for (int iFilter = 0; iFilter < cFilters; iFilter++)
        {
            newFilter = new AddressFilter();
            newFilter.DeserializeFromBinaryReader(biReader, memStream);
            filters.Add(newFilter);
        }

        return filters;
    }

    public override string ToString()
    {
        return _filter;
    }
}

Each item tracks whether it is in a filter, and the version of the change that caused the item to move into or out of the filter. This example stores the filter tracking metadata for an item as custom item fields in the metadata store.

// Allocate space for the filter tracking metadata for each tracked filter.
private void InitializeFilterTrackingFields(ItemMetadata itemMeta)
{
    if (0 < _trackedFilters.Count)
    {
        byte[] newIsInFilterBytes = new byte[_trackedFilters.Count];
        byte[] newMoveVersionBytes = new byte[_trackedFilters.Count * (sizeof(uint) + sizeof(ulong))];
        itemMeta.SetCustomField(IsInFiltersField, newIsInFilterBytes);
        itemMeta.SetCustomField(MoveVersionsField, newMoveVersionBytes);
    }
}

// Gets a value that indicates whether the specified item is in the specified filter,
// according to the filter tracking metadata.
private bool GetIsInFilter(ItemMetadata itemMeta, int iFilter)
{
    byte[] isInFilterList = itemMeta.GetBytesField(IsInFiltersField);
    return (1 == isInFilterList[iFilter]);
}

// Sets a value that indicates whether the specified item is in the specified filter.
private void SetIsInFilter(ItemMetadata itemMeta, int iFilter, bool isInFilter)
{
    byte[] isInFilterList = itemMeta.GetBytesField(IsInFiltersField);
    isInFilterList[iFilter] = (byte)(isInFilter ? 1 : 0);
    itemMeta.SetCustomField(IsInFiltersField, isInFilterList);
}

// Gets the version of the change that caused the specified item to move in relation
// to the specified filter.
private SyncVersion GetMoveVersion(ItemMetadata itemMeta, int iFilter)
{
    // Get the raw bytes for the move version list.
    byte[] moveVersionBytes = itemMeta.GetBytesField(MoveVersionsField);

    // Read the SyncVersion elements from the specified location in the byte array.
    MemoryStream memStream = new MemoryStream(moveVersionBytes);
    memStream.Seek(iFilter * (sizeof(uint) + sizeof(ulong)), SeekOrigin.Begin);
    BinaryReader biReader = new BinaryReader(memStream, Encoding.Unicode);
    uint replicaKey = biReader.ReadUInt32();
    ulong tickCount = biReader.ReadUInt64();

    SyncVersion moveVersion = new SyncVersion(replicaKey, tickCount);

    return moveVersion;
}

// Sets the version of the change that caused the specified item to move in relation
// to the specified filter.
private void SetMoveVersion(ItemMetadata itemMeta, int iFilter, SyncVersion moveVersion)
{
    // Get the raw bytes for the move version list.
    byte[] moveVersionBytes = itemMeta.GetBytesField(MoveVersionsField);

    // Write the SyncVersion elements to the specified location in the byte array.
    MemoryStream memStream = new MemoryStream(moveVersionBytes);
    memStream.Seek(iFilter * (sizeof(uint) + sizeof(ulong)), SeekOrigin.Begin);
    BinaryWriter biWriter = new BinaryWriter(memStream, Encoding.Unicode);
    biWriter.Write(moveVersion.ReplicaKey);
    biWriter.Write(moveVersion.TickCount);

    itemMeta.SetCustomField(MoveVersionsField, moveVersionBytes);
}

// Set up fields used to track a new filter.
public bool StartTrackingFilter(AddressFilter filter)
{
    bool filterIsNew = true;

    foreach (AddressFilter addressFilter in _trackedFilters)
    {
        if (addressFilter.IsIdentical(filter))
        {
            filterIsNew = false;
            break;
        }
    }

    if (filterIsNew)
    {
        // Initialize the filter forgotten knowledge to the current knowledge of the replica.
        filter.FilterForgottenKnowledge = new ForgottenKnowledge(ContactStore.ContactIdFormatGroup,
            ContactReplicaMetadata.GetKnowledge());
        _trackedFilters.Add(filter);

        // Allocate new space for and initialize filter tracking metadata for all active items.
        byte[] newIsInFilterBytes = new byte[_trackedFilters.Count];
        byte[] newMoveVersionBytes = new byte[_trackedFilters.Count * (sizeof(uint) + sizeof(ulong))];
        int iFilter = _trackedFilters.Count - 1;
        foreach (ItemMetadata itemMeta in _ContactItemMetaList.Values)
        {
            // Get current filter tracking metadata, copy it to the new byte arrays, and store it.
            byte[] isInFilterBytes = itemMeta.GetBytesField(IsInFiltersField);
            byte[] moveVersionBytes = itemMeta.GetBytesField(MoveVersionsField);

            if (null != isInFilterBytes)
            {
                isInFilterBytes.CopyTo(newIsInFilterBytes, 0);
            }
            if (null != moveVersionBytes)
            {
                moveVersionBytes.CopyTo(newMoveVersionBytes, 0);
            }

            itemMeta.SetCustomField(IsInFiltersField, newIsInFilterBytes);
            itemMeta.SetCustomField(MoveVersionsField, newMoveVersionBytes);

            // Initialize filter tracking metadata.
            bool isInFilter = filter.IsInFilter(_ContactList[itemMeta.GlobalId]);
            SetIsInFilter(itemMeta, iFilter, isInFilter);
            if (isInFilter)
            {
                // If the item is in the filter, set the move version to the change version for the item.
                // Otherwise, leave the move version as (0,0).
                SetMoveVersion(itemMeta, iFilter, itemMeta.ChangeVersion);
            }
        }

        // Update the list of filters that are stored in the custom replica metadata.
        AddressFilter.StoreFiltersInReplicaMetadata(ContactReplicaMetadata, TrackedFilters);
    }

    return filterIsNew;
}

// Gets the list of address filters that are tracked by this replica.
public List<AddressFilter> TrackedFilters
{
    get
    {
        return _trackedFilters;
    }
}
private List<AddressFilter> _trackedFilters;

Next Steps

Next, you might want to add filter negotiation to your provider so that it can communicate with the destination provider to establish which filter to use for change enumeration. For more information on how to negotiate filters, see How to: Negotiate a Filter.

You may also want to enable your provider to represent a filtered replica when it is the destination provider. For more information on how to implemented a filtered provider, see How to: Filter a Replica.

See Also

Concepts

Programming Common Standard Custom Provider Tasks
Filtering Synchronization Data