HOW TO:篩選複寫
本主題描述如何使用 Managed 語言來實作代表篩選複寫的 Sync Framework 提供者。篩選複寫只會針對位於篩選中的項目或變更單位儲存資料。
本主題假設您對於 C# 和 Microsoft .NET Framework 概念有基本的了解。
本主題的範例將重點放在下列 Sync Framework 類別和成員:
了解篩選複寫
篩選複寫會儲存只有位於其篩選中之項目和變更單位的項目和變更單位資料,以及準刪除項目,也就是原本位於篩選內但是已經移出之項目和變更單位的中繼資料。此外,篩選複寫也會追蹤其篩選,而且可能還會追蹤其他篩選。篩選複寫可以與來源提供者交涉篩選,而在此情況下,來源提供者會產生篩選變更批次。如果來源提供者無法產生篩選變更批次,篩選提供者就可以自行篩選變更並且只套用位於其篩選中的變更。
篩選提供者會實作 IFilteredReplicaNotifyingChangeApplierTarget,以便向變更套用者傳達有關以相對於篩選之方式移動的項目資訊。此外,篩選提供者通常也會實作 IRequestFilteredSync,以便能夠交涉來源提供者用於列舉變更的篩選。
組建需求
.NET Framework 2.0 或更新版本。
範例
本主題的範例程式碼將示範如何實作篩選目的地提供者。篩選提供者會要求包含準刪除項目的篩選變更批次,並且將篩選變更和準刪除項目套用至目的地複寫。這個範例中的複寫是一個文字檔,這個檔案會將連絡人資訊儲存為以逗號分隔的值清單。要同步處理的項目是這個檔案中所包含的連絡人。篩選是一個字串,只有當系統在連絡人的地址欄位中找到篩選字串時,才會導致加入連絡人。
交涉篩選
當 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();
}
後續的步驟
接著,您可能會想要將篩選交涉新增至提供者,讓它能夠與目的地提供者通訊,以便確立要用於變更列舉的篩選。如需如何交涉篩選的詳細資訊,請參閱 HOW TO:交涉篩選。
此外,您可能也會想要讓提供者追蹤篩選。將變更傳送至篩選複寫時,篩選追蹤複寫會保持較小的知識尺寸。如需如何實作篩選追蹤提供者的詳細資訊,請參閱 HOW TO:追蹤篩選和列舉篩選變更。