Answered by:
Custom provider sync

Question
-
I have a custom provider that is used as both source provider and destination provider during a sync session. Initially both stores have one item with the same key, and local constraint resolution policy is "SourceWins", and remote constraint resolution policy is "DestinationWins".
Initial State
Store A: Item_A1, Key="1", Value="ABC"
Store B: Item_B1, Key="1", Value ="DEF"
During upload everything goes as expected: 1)Create action failed and constraint is logged 2)DeleteAndSaveTombstone action saves a tombstone for Item_A1.
During download, however, sometimes Create action is triggered first, and sometimes DeleteAndSaveTombstone action is triggered first.
When Create action is triggered first I got an Invalid cast exception:
Test method DataCladUnitTests.StandardProviderTest.ConstraintConflictUploadDownload_SourceWins_DestinationWins threw exception: System.InvalidCastException: Specified cast is not valid..
Microsoft.Synchronization.CoreInterop.ISynchronousNotifyingChangeApplier2.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
Microsoft.Synchronization.NotifyingChangeApplier.ISynchronousNotifyingChangeApplierImpl.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, CollisionConflictResolutionPolicy collisionConflictResolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, IConflictLogAccess conflictLogAccess, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
Memeo.DataClad2.Sync.StandardSyncProvider.ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, Object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics) in C:\svn\dataclad\trunk\Memeo.DataClad2.Sync\StandardSyncProvider.cs: line 88
Microsoft.Synchronization.KnowledgeProviderProxy.ProcessChangeBatch(CONFLICT_RESOLUTION_POLICY resolutionPolicy, ISyncChangeBatch pSourceChangeManager, Object pUnkDataRetriever, ISyncCallback pCallback, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
Microsoft.Synchronization.SyncOrchestrator.Synchronize()
DataCladUnitTests.StandardProviderTest.ConstraintConflictUploadDownload_SourceWins_DestinationWins() in C:\svn\dataclad\trunk\Tests\DataCladUnitTests\StandardProviderTest.cs: line 221When DeleteAndSaveTombstone action is triggered first I got:
Test method DataCladUnitTests.StandardProviderTest.ConstraintConflictUploadDownload_SourceWins_DestinationWins threw exception: Microsoft.Synchronization.MetadataStorage.KeyUniquenessException: The metadata store item could not be saved as it does not have a unique ID (or other primary key).
---> System.Runtime.InteropServices.COMException: The metadata store item could not be saved as it does not have a unique ID (or other primary key).
.Microsoft.Synchronization.MetadataStorage.IReplicaMetadata2.SaveItemMetadata(IItemMetadata pItemMetadata)
Microsoft.Synchronization.MetadataStorage.SqlReplicaMetadata.SaveItemMetadata(ItemMetadata itemMetadata)
Microsoft.Synchronization.MetadataStorage.SqlReplicaMetadata.SaveItemMetadata(ItemMetadata itemMetadata)
Memeo.DataClad2.Sync.StandardSyncProvider.saveItemMetadata(ItemMetadata item) in C:\svn\dataclad\trunk\Memeo.DataClad2.Sync\StandardSyncProvider.cs: line 315
Memeo.DataClad2.Sync.StandardSyncProvider.SaveItemChange(SaveChangeAction saveChangeAction, ItemChange change, SaveChangeContext context) in C:\svn\dataclad\trunk\Memeo.DataClad2.Sync\StandardSyncProvider.cs: line 175
Microsoft.Synchronization.NotifyingChangeApplierTargetProxy.SaveChange(SYNC_SAVE_ACTION ssa, ISyncChange pChange, ISaveChangeContext pSaveChangeContext)
Microsoft.Synchronization.CoreInterop.ISynchronousNotifyingChangeApplier2.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
Microsoft.Synchronization.NotifyingChangeApplier.ISynchronousNotifyingChangeApplierImpl.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, CollisionConflictResolutionPolicy collisionConflictResolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, IConflictLogAccess conflictLogAccess, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
Memeo.DataClad2.Sync.StandardSyncProvider.ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, Object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics) in C:\svn\dataclad\trunk\Memeo.DataClad2.Sync\StandardSyncProvider.cs: line 88
Microsoft.Synchronization.KnowledgeProviderProxy.ProcessChangeBatch(CONFLICT_RESOLUTION_POLICY resolutionPolicy, ISyncChangeBatch pSourceChangeManager, Object pUnkDataRetriever, ISyncCallback pCallback, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
Microsoft.Synchronization.SyncOrchestrator.Synchronize()
DataCladUnitTests.StandardProviderTest.ConstraintConflictUploadDownload_SourceWins_DestinationWins() in C:\svn\dataclad\trunk\Tests\DataCladUnitTests\StandardProviderTest.cs: line 221And here is my SaveItemChange method
public void SaveItemChange(SaveChangeAction saveChangeAction, ItemChange change, SaveChangeContext context) { ItemMetadata item = null; IStoreItem data = null; string key; switch (saveChangeAction) { case SaveChangeAction.Create: //Do duplicate detection here item = mMetaManager.Metadata.FindItemMetadataById(change.ItemId); if (null != item) { throw new Exception("SaveItemChange must not have Create action for existing items."); } data = (IStoreItem)context.ChangeData; ConstraintConflictType constraintType; if (!mDataSource.CanCreateItem(data, out key, out constraintType)) { //Constraint conflict detected. ItemMetadata constraintItem = mMetaManager.Metadata.FindItemMetadataByUniqueIndexedField(mMetaManager.StoreItemKeyColumn, key); context.RecordConstraintConflictForItem(constraintItem.GlobalId, SyncFrameUtils.ToConstraintConflictReason(constraintType)); mConflictLog.SaveConstraintConflict(change, constraintItem.GlobalId, SyncFrameUtils.ToConstraintConflictReason(constraintType), context.ChangeData, new SyncKnowledge(IdFormats, mMetaManager.ReplicaId, mMetaManager.Metadata.GetNextTickCount()), true); //TODO: permernant conflict? } else { item = mMetaManager.Metadata.CreateItemMetadata(change.ItemId, change.CreationVersion); item.ChangeVersion = change.ChangeVersion; key = mDataSource.CreateItem(data); item.SetCustomField(mMetaManager.StoreItemKeyColumn, key); item.SetCustomField(mMetaManager.StoreItemRevisionColumn, mDataSource.GetItem(key).Revision); saveItemMetadata(item); } break; case SaveChangeAction.UpdateVersionAndData: case SaveChangeAction.UpdateVersionOnly: item = mMetaManager.Metadata.FindItemMetadataById(change.ItemId); if (null == item) throw new Exception("Item Not Found in Store!?"); item.ChangeVersion = change.ChangeVersion; if (saveChangeAction == SaveChangeAction.UpdateVersionOnly) { saveItemMetadata(item); } else //Also update the data and the timestamp. { data = (IStoreItem)context.ChangeData; mDataSource.UpdateItem(data); saveItemMetadata(item); } break; case SaveChangeAction.DeleteAndStoreTombstone: item = mMetaManager.Metadata.FindItemMetadataById(change.ItemId); if (null == item) { item = mMetaManager.Metadata.CreateItemMetadata(change.ItemId, change.CreationVersion); //item.SetCustomField(mMetaManager.StoreItemKeyColumn, (mDataSource.Count+1).ToString()); } else mDataSource.DeleteItem(item.GetStringField(mMetaManager.StoreItemKeyColumn)); if (change.ChangeKind == ChangeKind.Deleted) { item.MarkAsDeleted(change.ChangeVersion); } else { // This should never happen in Sync Framework V1.0 throw new Exception("Invalid ChangeType"); } item.ChangeVersion = change.ChangeVersion; saveItemMetadata(item); // TOTO: set timestamp to 0 for tombstones (other means to remove tombstones) break; //Merge the changes! (Take the data from the local item + the remote item),noting to update the tick count to propagate the resolution! case SaveChangeAction.UpdateVersionAndMergeData: item = mMetaManager.Metadata.FindItemMetadataById(change.ItemId); if (null == item) throw new Exception("Item Not Found in Store!?"); if (item.IsDeleted != true) { //Note - you must update the change version to propagate the resolution! item.ChangeVersion = new SyncVersion(0, mMetaManager.Metadata.GetNextTickCount()); key = item.GetStringField(mMetaManager.StoreItemKeyColumn); //Combine the conflicting data... IStoreItem mergedData = mDataSource.MergeItem(mDataSource.GetItem(key), (IStoreItem)context.ChangeData); saveItemMetadata(item); } break; case SaveChangeAction.DeleteAndRemoveTombstone: item = mMetaManager.Metadata.FindItemMetadataById(change.ItemId); if (item != null) { List<SyncId> ids = new List<SyncId>(); ids.Add(item.GlobalId); mMetaManager.Metadata.RemoveItemMetadata(ids); } key = item.GetStringField(mMetaManager.StoreItemKeyColumn); mDataSource.DeleteItem(key); break; case SaveChangeAction.DeleteConflictingAndSaveSourceItem: item = mMetaManager.Metadata.FindItemMetadataById(context.ConflictingItemId); if (item != null) { List<SyncId> ids = new List<SyncId>(); ids.Add(item.GlobalId); mMetaManager.Metadata.RemoveItemMetadata(ids); } key = item.GetStringField(mMetaManager.StoreItemKeyColumn); mDataSource.DeleteItem(key); item = mMetaManager.Metadata.CreateItemMetadata(change.ItemId, change.CreationVersion); item.ChangeVersion = change.ChangeVersion; data = (IStoreItem)context.ChangeData; key = mDataSource.CreateItem(data); item.SetCustomField(mMetaManager.StoreItemKeyColumn, key); //TODO: The readback may be inefficient to remote stores item.SetCustomField(mMetaManager.StoreItemRevisionColumn, mDataSource.GetItem(key).Revision); saveItemMetadata(item); break; } }
And I'm trying to catch up with store changes when session starts:private void updateMetadataStoreWithLocalChanges() { SyncId id; CultureInfo info; SyncVersion newVersion = new SyncVersion(0, mMetaManager.Metadata.GetNextTickCount()); mMetaManager.GetMetadataStore(out id, out info).BeginTransaction(System.Data.IsolationLevel.Serializable); mMetaManager.Metadata.DeleteDetector.MarkAllItemsUnreported(); foreach (IStoreItem data in mDataSource.ListAllItems()) { //Look up an item's metadata by its ID... ItemMetadata item = mMetaManager.Metadata.FindItemMetadataByUniqueIndexedField(mMetaManager.StoreItemKeyColumn, data.Key.ToString()); if (null == item) { //New item, must have been created since that last time the metadata was updated. //Create the item metadata required for sync (giving it a SyncID and a version, defined to be a DWORD and a ULONGLONG //For creates, simply provide the relative replica ID (0) and the tick count for the provider (ever increasing) item = mMetaManager.Metadata.CreateItemMetadata(new SyncId(new SyncGlobalId((ulong)mDataSource.Count, Guid.NewGuid())), newVersion); item.SetCustomField(mMetaManager.StoreItemKeyColumn, data.Key); item.SetCustomField(mMetaManager.StoreItemRevisionColumn, data.Revision); item.ChangeVersion = newVersion; saveItemMetadata(item); } else { if (data.Revision > item.GetUInt64Field(mMetaManager.StoreItemRevisionColumn)) { item.ChangeVersion = newVersion; saveItemMetadata(item); } else mMetaManager.Metadata.DeleteDetector.ReportLiveItemById(item.GlobalId); } } //Now go back through the items that are no longer in the store and mark them as deleted in the metadata. //This sets the item as a tombstone. foreach (ItemMetadata item in mMetaManager.Metadata.DeleteDetector.FindUnreportedItems()) { item.MarkAsDeleted(newVersion); saveItemMetadata(item); // set timestamp to 0 for tombstones } mMetaManager.GetMetadataStore(out id, out info).CommitTransaction(); }
Questions: Why actions are triggered in different order? What did I do wrong? Thank you!Wednesday, May 5, 2010 7:39 PM
Answers
-
It seems a little wierd to me.
1. For sync to apply a simple new created item (no conflict case), I don't see the reason why the constraint type will have a value.
2. When resolving constraint conflict during upload sync, no new item is created and the constraint type has not be set in your implementation. During download sync, the creation item should not have a valid constaint type.
3. I believe that you have two places to set custom field. One is when your provider does the change enumeration to update local metadata store with latest local changes. Another is when your provider tries to apply incoming sync changes. For insert and update changes, you need to update the customer field values. For your provider, you have verified that the custom values are correct write to metadata store for both cases?
It will be very helpful if you can share your full implementation of the provider and local store.
Thanks,
Dong
This posting is provided AS IS with no warranties, and confers no rights.- Marked as answer by Friendly Dog IIMicrosoft employee Monday, May 10, 2010 10:51 PM
Monday, May 10, 2010 7:12 PM -
Friendly Dog II,
in order to use constraint conflict functionality properly, you need to consider several things:
- your replica needs to implement the INotifyingChangeApplierTarget2 interface (which is the SaveConstraintConflict() method). Don't call this method yourself (like you do in the SaveChange method). The engine will call this method on your replica. What you should do is just forward all the parameters to the MemoryLog.SaveConstraintConflict().
- when calling changeApplier.ApplyChanges(), make sure to call the override which takes as a parameter an IConflictLogAccess object, which by the way is the MemoryConflictLog where the temporary constraint conflicts are called.
- I assume you use the MemoryLog class we provide, correct (you did not implement you own MemoryConflictLog).?
You may want to take a more-in-depth look at the Sync101 with constraint conflicts sample, available here - http://code.msdn.microsoft.com/sync/Release/ProjectReleases.aspx?ReleaseId=3417
Here's another guide I think might be useful when preparing to handle constraint logic: http://m,sdn.microsoft.com/en-us/library/ee617384(SQL.105).aspx, and another example - http://msdn.microsoft.com/en-us/library/ee617360(SQL.105).aspx
Another thing to keep in mind is that when dealing with constraint conflicts, the items will NOT be applied in the order they have been enumerated/reported/batched. That is because the logic is pretty complex on the engine side, and sometimes the engine may break the order of items in order to achieve the correct result.
Hope this helps.
Adrian
- Marked as answer by Dong CaoMicrosoft employee Monday, May 10, 2010 10:35 PM
Monday, May 10, 2010 10:09 PM
All replies
-
Is there a complete custom provider sample that handle all conflicting scenarios?Thursday, May 6, 2010 11:46 PM
-
Hi,
There is no MSDN sample that handles all conflict scenarios. For constraint conflict, please reference "Sync101 with Constraint Conflicts" - http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=sync&ReleaseId=3417
For different action order, it is due to changes are sent by ItemId order. Sometimes the item for creation action has smaller item ID but some other times the deletion action item has smaller ID.
For the invalidCastException, please check if a new created item can be synced with this custom provider when there is no conflict.
For the KeyUniquenessException, the metadata store detects an item with the same itemId. I cannot tell why from your code snippet. It seems that the code tries to insert a row with same itemid instead of updating the existing one. I may need the full source file of your provider for further investigation.
When writting a complicated sync provider with Sync Framework library, you may want to start from simple scenarios and test them before adding more functionalities.
Thanks,
Dong
This posting is provided AS IS with no warranties, and confers no rights.- Proposed as answer by Dong CaoMicrosoft employee Monday, May 10, 2010 4:50 PM
Friday, May 7, 2010 1:48 AM -
Thank you Dong. Yes I've got basic scenarios working and now I'm working on conflict resolutions. Here's more detailed trace of what happened:
During upload
=========
1. Create. Item [112328] Create version {1,1}, Update version {1,1}
2. Constraint detected by data source. Conflicting Item [149acc] Create version {0,1}, Update version {0,1}
3. Constraint is logged
4. DeleteAndStoreTombStone. Item [112328] Create version {1,1}, Update version {0,5}
During download
===========
1. DeleteAndStoreTombStone. Item [112328] Create version {0,1}, Update version {1,5}
2. Create. Item [149acc] Create version {1,1}, Update version {1,1}
During the Create action I have code to save two custom fields:
item.SetCustomField(mMetaManager.StoreItemKeyColumn, key);
item.SetCustomField(mMetaManager.StoreItemRevisionColumn, mDataSource.GetItem(key).Revision);
What I found that if I comment these two lines out the sync session completes without exception. What's the reason for this? And anything you can see wrong in above traces? Thank you!
Monday, May 10, 2010 5:07 PM -
Hi,
If the two SetCustomField calls caused the same problem when just applying a new created item, you may want to check if you did add custom fieldschema for them when creating your ReplicaMetadata. Please check MetadataStore.InitializeReplicaMetadata. The IEnumerable<FieldSchema> customItemFieldSchemas should include the StoreItemKeyColumn and StoreItemRevisionColumn.
Thanks,
Dong
This posting is provided AS IS with no warranties, and confers no rights.Monday, May 10, 2010 6:03 PM -
Creating new item is fine with the two lines of code. When I tested sync one-way into the store everything worked fine.Monday, May 10, 2010 6:06 PM
-
In this case, your code should work for download creation as well. Can you check if the method: mDataSource.CanCreateItem(data, out key, out constraintType)) does return the correct key and constraintType in the error scenario? Also, after a intial creation sync, can you successfully fetch the custom field values from the metadata store in destination side?
Thanks,
Dong
This posting is provided AS IS with no warranties, and confers no rights.Monday, May 10, 2010 6:18 PM -
The method returns key correctly and it always returns constraint type as
ConstraintConflictType.CollisionI was able to read ths custom field back after initial creation using statements like:
item.GetStringField(mMetaManager.StoreItemKeyColumn);
Monday, May 10, 2010 6:30 PM -
It seems a little wierd to me.
1. For sync to apply a simple new created item (no conflict case), I don't see the reason why the constraint type will have a value.
2. When resolving constraint conflict during upload sync, no new item is created and the constraint type has not be set in your implementation. During download sync, the creation item should not have a valid constaint type.
3. I believe that you have two places to set custom field. One is when your provider does the change enumeration to update local metadata store with latest local changes. Another is when your provider tries to apply incoming sync changes. For insert and update changes, you need to update the customer field values. For your provider, you have verified that the custom values are correct write to metadata store for both cases?
It will be very helpful if you can share your full implementation of the provider and local store.
Thanks,
Dong
This posting is provided AS IS with no warranties, and confers no rights.- Marked as answer by Friendly Dog IIMicrosoft employee Monday, May 10, 2010 10:51 PM
Monday, May 10, 2010 7:12 PM -
1. In the case of no conflict contraint type is not used (See SaveChangeAction.Create branch of SaveItemChange method)
2. In my understanding during upload because of "DestinationWins" what happens is a tombstone is recorded for the item that failed to sync (so it can be removed from sync community). There's no new item created during this process:
1) First the item is attempted to be added to destination
2) destination reports constratint conflict, the conflict is logged
3) A tombstone is recorded for failed item
Am I missing something here?
3. Yes the custom fields are set in both cases.
I can separte the code out into a test project. What's the best way to share the code?
Monday, May 10, 2010 8:54 PM -
Friendly Dog II,
in order to use constraint conflict functionality properly, you need to consider several things:
- your replica needs to implement the INotifyingChangeApplierTarget2 interface (which is the SaveConstraintConflict() method). Don't call this method yourself (like you do in the SaveChange method). The engine will call this method on your replica. What you should do is just forward all the parameters to the MemoryLog.SaveConstraintConflict().
- when calling changeApplier.ApplyChanges(), make sure to call the override which takes as a parameter an IConflictLogAccess object, which by the way is the MemoryConflictLog where the temporary constraint conflicts are called.
- I assume you use the MemoryLog class we provide, correct (you did not implement you own MemoryConflictLog).?
You may want to take a more-in-depth look at the Sync101 with constraint conflicts sample, available here - http://code.msdn.microsoft.com/sync/Release/ProjectReleases.aspx?ReleaseId=3417
Here's another guide I think might be useful when preparing to handle constraint logic: http://m,sdn.microsoft.com/en-us/library/ee617384(SQL.105).aspx, and another example - http://msdn.microsoft.com/en-us/library/ee617360(SQL.105).aspx
Another thing to keep in mind is that when dealing with constraint conflicts, the items will NOT be applied in the order they have been enumerated/reported/batched. That is because the logic is pretty complex on the engine side, and sometimes the engine may break the order of items in order to achieve the correct result.
Hope this helps.
Adrian
- Marked as answer by Dong CaoMicrosoft employee Monday, May 10, 2010 10:35 PM
Monday, May 10, 2010 10:09 PM -
Thank you Adrian and Dong for help, especially the last post of Adrian. I've managed to get the constraint resolved as expected. Here are list of mistakes I made:
1. I implemented INotifyingChangeApplierTarget instead of INotifyingChangeApplierTarget2
2. I called SaveConstraintConflict method myself
3. My custom column is set to "unique index", which caused the "invalid cast" problem during creation because the unique key violation.
Monday, May 10, 2010 10:51 PM