/// <summary> /// Get the conflict item /// </summary> /// <returns>Conflict item</returns> public virtual Conflict GetConflict() { if (!HasConflict()) { return(null); } Conflict conflict; if (currentEntryWrapper.IsConflict) { conflict = new SyncConflict { LiveEntity = liveEntity, LosingEntity = ReflectionUtility.GetObjectForType(currentEntryWrapper.ConflictWrapper, knownTypes), Resolution = (SyncConflictResolution) Enum.Parse(FormatterConstants.SyncConflictResolutionType, currentEntryWrapper.ConflictDesc, true) }; } else { conflict = new SyncError { LiveEntity = liveEntity, ErrorEntity = ReflectionUtility.GetObjectForType(currentEntryWrapper.ConflictWrapper, knownTypes), Description = currentEntryWrapper.ConflictDesc }; } return(conflict); }
public ApplyChangeFailedEventArgs(SyncConflict dbSyncConflict, ApplyAction action, DbConnection connection, DbTransaction transaction) { this._syncConflict = dbSyncConflict; this._connection = connection; this._transaction = transaction; this._applyAction = action; }
/// <summary> /// Get the conflict item /// </summary> /// <returns>Conflict item</returns> public virtual Conflict GetConflict() { if (!HasConflict()) { return(null); } Conflict conflict = null; if (_currentEntryWrapper.IsConflict) { conflict = new SyncConflict { LiveEntity = _liveEntity, LosingEntity = CreateEntity(_currentEntryWrapper.ConflictWrapper, _knownTypesDict), Resolution = (SyncConflictResolution)Enum.Parse(FormatterConstants.SyncConflictResolutionType, _currentEntryWrapper.ConflictDesc, true) }; } else { conflict = new SyncError { LiveEntity = _liveEntity, ErrorEntity = CreateEntity(_currentEntryWrapper.ConflictWrapper, _knownTypesDict), Description = _currentEntryWrapper.ConflictDesc }; } return(conflict); }
/// <summary> /// Sets the sync error to null. This method is not thread safe. /// </summary> internal void UnsafeClearSyncError() { syncConflict = null; OnPropertyChanged("SyncConflict"); OnPropertyChanged("HasSyncConflict"); }
/// <summary> /// Called by SyncErrorInfo from the ClearSyncConflict method. Used to remove the conflict /// from the collection and from the disk /// </summary> /// <param name="conflict">conflict to clear</param> internal void ClearSyncConflict(SyncConflict conflict) { using (_saveSyncLock.LockObject()) { _cacheData.RemoveSyncConflict(conflict); _storageHandler.ClearSyncConflict((IsolatedStorageSyncConflict)conflict); } }
/// <summary> /// Clears the sync conflict and removes it from the conflict list on the c. /// </summary> public async Task ClearSyncConflict() { if (syncConflict != null) { await context.ClearSyncConflict(syncConflict); syncConflict = null; OnPropertyChanged("SyncConflict"); OnPropertyChanged("HasSyncConflict"); } }
/// <summary> /// Adds a conflict to the list of in-memory /// </summary> /// <param name="conflict">Conflict to add</param> /// <param name="context">Context for which the conflict is being added</param> public void AddSyncConflict(SyncConflict conflict, WinEightContext context) { OfflineEntity entity = Collections[conflict.LiveEntity.GetType()].AddOrUpdateSyncEntity((OfflineEntity)conflict.LiveEntity); SyncConflict oldConflict = Collections[conflict.LiveEntity.GetType()].MapSyncConflict(entity, conflict, context); SyncConflicts.Add(conflict); if (oldConflict != null) { ClearSyncConflict(oldConflict, context); } }
public void AddSerializedConflict(SyncConflict conflict, IsolatedStorageOfflineContext context) { Type entityType = conflict.LiveEntity.GetType(); IsolatedStorageOfflineEntity entity = Collections[entityType].AddOrUpdateSyncEntity((IsolatedStorageOfflineEntity)conflict.LiveEntity); IsolatedStorageSyncConflict oldConflict = (IsolatedStorageSyncConflict)Collections[entityType].MapSyncConflict(entity, conflict, context); SyncConflicts.Add(conflict); if (oldConflict != null) { ClearSyncConflict(oldConflict, context); } }
/// <summary> /// Sets the sync conflict, providing the cache data so that the conflict can be removed if ClearSyncConflict /// is called. /// </summary> ///<param name="c">IsolatedStorageOfflineContext</param> /// <param name="sc">conflict to set.</param> internal void SetSyncConflict(WinEightContext c, SyncConflict sc) { SyncConflict oldConflict = this.syncConflict; this.context = c; this.syncConflict = sc; OnPropertyChanged("SyncConflict"); if (oldConflict == null) { OnPropertyChanged("HasSyncConflict"); } }
/// <summary> /// Called by SyncErrorInfo from the ClearSyncConflict method. Used to remove the conflict /// from the collection and from the disk /// </summary> /// <param name="conflict">conflict to clear</param> internal async Task ClearSyncConflict(SyncConflict conflict) { using (saveSyncLock.LockObject()) { WinEightSyncConflict winEightSyncConflict = conflict as WinEightSyncConflict; if (winEightSyncConflict == null) { return; } cacheData.RemoveSyncConflict(conflict); await StorageHandler.ClearSyncConflict(winEightSyncConflict); } }
/// <summary> /// Sets the sync conflict, providing the cache data so that the conflict can be removed if ClearSyncConflict /// is called. /// </summary> ///<param name="context">IsolatedStorageOfflineContext</param> /// <param name="syncConflict">conflict to set.</param> internal void SetSyncConflict( IsolatedStorageOfflineContext context, SyncConflict syncConflict) { SyncConflict oldConflict = this._syncConflict; this._context = context; this._syncConflict = syncConflict; OnPropertyChanged("SyncConflict"); if (oldConflict == null) { OnPropertyChanged("HasSyncConflict"); } }
private void ClearSyncConflict(SyncConflict syncConflict, IsolatedStorageOfflineContext context) { }
public void RemoveSyncConflict(SyncConflict conflict) { }
/// <summary> /// Removes the specified sync conflict /// </summary> /// <param name="conflict">Conflict to remove</param> public void RemoveSyncConflict(SyncConflict conflict) { SyncConflicts.Remove(conflict); }
private void ClearSyncConflict(SyncConflict syncConflict, OfflineContext context) { }
private SyncConflict CreateSyncError(UpdateResult r, DataRow clientRow) { Microsoft.Synchronization.SyncStage syncStage = Microsoft.Synchronization.SyncStage.UploadingChanges; switch (r.Command) { case "New": syncStage = Microsoft.Synchronization.SyncStage.ApplyingInserts; break; case "Update": syncStage = Microsoft.Synchronization.SyncStage.ApplyingUpdates; break; case "Delete": syncStage = Microsoft.Synchronization.SyncStage.ApplyingDeletes; break; } SyncConflict conflict; if (r.ErrorCode == UpdateResult.VersionConflict) { if (r.Command == "Update") { conflict = new SyncConflict(ConflictType.ClientUpdateServerUpdate, syncStage); } else if (r.Command == "Delete") { conflict = new SyncConflict(ConflictType.ClientDeleteServerUpdate, syncStage); } else { conflict = new SyncConflict(ConflictType.Unknown, syncStage); } if (r.ItemData != null) { Exception e; DataRow serverRow = clientRow.Table.NewRow(); MapListItemToDataRow(r.ItemData, serverRow, out e); if (e == null) { conflict.ServerChange = serverRow.Table.Clone(); conflict.ServerChange.TableName = this.TableName; conflict.ServerChange.Rows.Add(serverRow); } } } else if (r.ErrorCode == UpdateResult.ItemDeleted) { conflict = new SyncConflict(ConflictType.ClientUpdateServerDelete, syncStage); } else { conflict = new SyncConflict(ConflictType.ErrorsOccurred, syncStage); } if (conflict.ClientChange == null) { conflict.ClientChange = clientRow.Table.Clone(); conflict.ClientChange.TableName = clientRow.Table.TableName; } conflict.ClientChange.ImportRow(clientRow); conflict.ErrorMessage = r.ErrorMessage; return(conflict); }
/// #UPLOAD 2 private void ApplyChangesInternal(SyncGroupMetadata groupMetadata, DataSet dataSet, SyncSession syncSession, SyncContext syncContext) { SyncStage syncStage = SyncStage.UploadingChanges; foreach (SyncTableMetadata tableMetadata in groupMetadata.TablesMetadata) { SpSyncAdapter adapter = null; if (this.SyncAdapters.Contains(tableMetadata.TableName)) { adapter = this.SyncAdapters[tableMetadata.TableName]; } if (adapter == null) { throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Messages.InvalidTableName, tableMetadata.TableName)); } // SpSyncAnchor anchor if (!dataSet.Tables.Contains(tableMetadata.TableName)) { throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Messages.TableNotInSchema, tableMetadata.TableName)); } SyncTableProgress tableProgress = syncContext.GroupProgress.FindTableProgress(tableMetadata.TableName); DataTable dataTable = dataSet.Tables[tableMetadata.TableName]; try { Collection <SyncConflict> conflicts; int changesCount = dataTable.Rows.Count; adapter.Update(dataTable, Connection, out conflicts); if (conflicts != null) { foreach (SyncConflict conflict in conflicts) { ApplyChangeFailedEventArgs failureArgs = new ApplyChangeFailedEventArgs(tableMetadata, conflict, null, syncSession, syncContext, Connection, null); OnApplyChangeFailed(failureArgs); if (failureArgs.Action == ApplyAction.Continue) { if (conflict != null) { tableProgress.ChangesFailed++; tableProgress.Conflicts.Add(conflict); } } } } tableProgress.ChangesApplied = changesCount - tableProgress.ChangesFailed; } catch (Exception e) { SyncConflict conflict = new SyncConflict(ConflictType.ErrorsOccurred, SyncStage.UploadingChanges) { ErrorMessage = e.Message + ", InnerException:" + e.InnerException.ToString(), ServerChange = dataTable, ClientChange = dataTable }; ApplyChangeFailedEventArgs failureArgs = new ApplyChangeFailedEventArgs(tableMetadata, conflict, null, syncSession, syncContext, Connection, null); OnApplyChangeFailed(failureArgs); // handle errors? if (SyncTracer.IsErrorEnabled()) { SyncTracer.Error(e.ToString()); } } SyncProgressEventArgs args = new SyncProgressEventArgs(tableMetadata, tableProgress, groupMetadata, syncContext.GroupProgress, syncStage); OnSyncProgress(args); } }
private async void ClearSyncConflict(SyncConflict syncConflict, WinEightContext context) { RemoveSyncConflict(syncConflict); await context.ClearSyncConflict(syncConflict); }
/// <summary> /// Removes the specified sync conflict /// </summary> /// <param name="conflict">Conflict to remove</param> public void RemoveSyncConflict(SyncConflict conflict) { SyncConflicts.Remove(conflict); }
private void ClearSyncConflict(SyncConflict syncConflict, IsolatedStorageOfflineContext context) { RemoveSyncConflict(syncConflict); context.StorageHandler.ClearSyncConflict((IsolatedStorageSyncConflict)syncConflict); }
private SyncConflict CreateSyncError(UpdateResult r, DataRow clientRow) { Microsoft.Synchronization.SyncStage syncStage = Microsoft.Synchronization.SyncStage.UploadingChanges; switch (r.Command) { case "New": syncStage = Microsoft.Synchronization.SyncStage.ApplyingInserts; break; case "Update": syncStage = Microsoft.Synchronization.SyncStage.ApplyingUpdates; break; case "Delete": syncStage = Microsoft.Synchronization.SyncStage.ApplyingDeletes; break; } SyncConflict conflict; if (r.ErrorCode == UpdateResult.VersionConflict) { if (r.Command == "Update") conflict = new SyncConflict(ConflictType.ClientUpdateServerUpdate, syncStage); else if (r.Command == "Delete") conflict = new SyncConflict(ConflictType.ClientDeleteServerUpdate, syncStage); else conflict = new SyncConflict(ConflictType.Unknown, syncStage); if (r.ItemData != null) { Exception e; DataRow serverRow = clientRow.Table.NewRow(); MapListItemToDataRow(r.ItemData, serverRow, out e); if (e == null) { conflict.ServerChange = serverRow.Table.Clone(); conflict.ServerChange.TableName = this.TableName; conflict.ServerChange.Rows.Add(serverRow); } } } else if (r.ErrorCode == UpdateResult.ItemDeleted) { conflict = new SyncConflict(ConflictType.ClientUpdateServerDelete, syncStage); } else { conflict = new SyncConflict(ConflictType.ErrorsOccurred, syncStage); } if (conflict.ClientChange == null) { conflict.ClientChange = clientRow.Table.Clone(); conflict.ClientChange.TableName = clientRow.Table.TableName; } conflict.ClientChange.ImportRow(clientRow); conflict.ErrorMessage = r.ErrorMessage; return conflict; }
public abstract SyncConflict MapSyncConflict(IsolatedStorageOfflineEntity entity, SyncConflict conflict, IsolatedStorageOfflineContext context);
public IsolatedStorageSyncConflict(SyncConflict conflict) { this.LiveEntity = conflict.LiveEntity; this.LosingEntity = conflict.LosingEntity; this.Resolution = conflict.Resolution; }
public IsolatedStorageSyncConflict(SyncConflict conflict) { this.LiveEntity = conflict.LiveEntity; this.LosingEntity = conflict.LosingEntity; this.Resolution = conflict.Resolution; }
private void ClearSyncConflict(SyncConflict syncConflict, IsolatedStorageOfflineContext context) { RemoveSyncConflict(syncConflict); context.StorageHandler.ClearSyncConflict((IsolatedStorageSyncConflict)syncConflict); }
public abstract SyncConflict MapSyncConflict(IsolatedStorageOfflineEntity entity, SyncConflict conflict, IsolatedStorageOfflineContext context);
/// <summary> /// Get the conflict item /// </summary> /// <returns>Conflict item</returns> public virtual Conflict GetConflict() { if (!HasConflict()) { return null; } Conflict conflict; if (currentEntryWrapper.IsConflict) { conflict = new SyncConflict { LiveEntity = liveEntity, LosingEntity = ReflectionUtility.GetObjectForType(currentEntryWrapper.ConflictWrapper, knownTypes), Resolution = (SyncConflictResolution) Enum.Parse(FormatterConstants.SyncConflictResolutionType, currentEntryWrapper.ConflictDesc, true) }; } else { conflict = new SyncError { LiveEntity = liveEntity, ErrorEntity = ReflectionUtility.GetObjectForType(currentEntryWrapper.ConflictWrapper, knownTypes), Description = currentEntryWrapper.ConflictDesc }; } return conflict; }
/// <summary> /// Handle a conflict /// </summary> internal ChangeApplicationAction HandleConflict(SyncConflict conflict, ScopeInfo scope, long timestamp, out DmRow finalRow) { finalRow = null; // overwrite apply action if we handle it (ie : user wants to change the action) if (this.ConflictActionInvoker != null) { ConflictApplyAction = this.ConflictActionInvoker(conflict, Connection, Transaction); } // Default behavior and an error occured if (ConflictApplyAction == ApplyAction.Rollback) { Logger.Current.Info("Rollback all operation"); return(ChangeApplicationAction.Rollback); } // Server wins if (ConflictApplyAction == ApplyAction.Continue) { Logger.Current.Info("Local Wins, update metadata"); // COnflict on a line that is not present on the datasource if (conflict.LocalChange == null || conflict.LocalChange.Rows == null || conflict.LocalChange.Rows.Count == 0) { return(ChangeApplicationAction.Continue); } if (conflict.LocalChange != null && conflict.LocalChange.Rows != null && conflict.LocalChange.Rows.Count > 0) { var localRow = conflict.LocalChange.Rows[0]; // TODO : Différencier le timestamp de mise à jour ou de création var updateMetadataCommand = GetCommand(DbObjectType.UpdateMetadataProcName); // create a localscope to override values var localScope = new ScopeInfo { Name = null, LastTimestamp = timestamp }; var rowsApplied = this.InsertOrUpdateMetadatas(updateMetadataCommand, localRow, localScope); if (rowsApplied < 1) { throw new Exception("No metadatas rows found, can't update the server side"); } finalRow = localRow; return(ChangeApplicationAction.Continue); } // tableProgress.ChangesFailed += 1; return(ChangeApplicationAction.Rollback); } // We gonna apply with force the remote line if (ConflictApplyAction == ApplyAction.RetryWithForceWrite) { if (conflict.RemoteChange.Rows.Count == 0) { Logger.Current.Error("Cant find a remote row"); return(ChangeApplicationAction.Rollback); } var row = conflict.RemoteChange.Rows[0]; bool operationComplete = false; // create a localscope to override values var localScope = new ScopeInfo { Name = scope.Name, LastTimestamp = timestamp }; if (conflict.Type == ConflictType.LocalNoRowRemoteUpdate || conflict.Type == ConflictType.LocalNoRowRemoteInsert) { operationComplete = this.ApplyInsert(row); } else if (applyType == DmRowState.Added) { operationComplete = this.ApplyInsert(row); } else if (applyType == DmRowState.Modified) { operationComplete = this.ApplyUpdate(row, localScope, true); } else if (applyType == DmRowState.Deleted) { operationComplete = this.ApplyDelete(row, localScope, true); } var insertMetadataCommand = GetCommand(DbObjectType.InsertMetadataProcName); var rowsApplied = this.InsertOrUpdateMetadatas(insertMetadataCommand, row, localScope); if (rowsApplied < 1) { throw new Exception("No metadatas rows found, can't update the server side"); } finalRow = row; //After a force update, there is a problem, so raise exception if (!operationComplete) { var ex = $"Can't force operation for applyType {applyType}"; Logger.Current.Error(ex); finalRow = null; return(ChangeApplicationAction.Rollback); } // tableProgress.ChangesApplied += 1; return(ChangeApplicationAction.Continue); } return(ChangeApplicationAction.Rollback); }
public object Convert(object value, Type targetType, object parameter, string language) { return(SyncConflict.GetConflictMessage((ConflictType)value)); }
public void AddSerializedConflict(SyncConflict conflict, OfflineContext context) { }
public ConflictEventArgs(Command client, Command server, SyncConflict conflict) { this.client = client; this.server = server; this.conflict = conflict; }
public abstract SyncConflict MapSyncConflict(OfflineEntity entity, SyncConflict conflict, WinEightContext context);
public WinEightSyncConflict(SyncConflict conflict) { this.LiveEntity = conflict.LiveEntity; this.LosingEntity = conflict.LosingEntity; this.Resolution = conflict.Resolution; }
/// <summary> /// Sets the sync conflict, providing the cache data so that the conflict can be removed if ClearSyncConflict /// is called. /// </summary> ///<param name="context">IsolatedStorageOfflineContext</param> /// <param name="syncConflict">conflict to set.</param> internal void SetSyncConflict( IsolatedStorageOfflineContext context, SyncConflict syncConflict) { SyncConflict oldConflict = this._syncConflict; this._context = context; this._syncConflict = syncConflict; OnPropertyChanged("SyncConflict"); if (oldConflict == null) { OnPropertyChanged("HasSyncConflict"); } }
/// <summary> /// We have a conflict, try to get the source (server) row and generate a conflict /// </summary> private SyncConflict GetConflict(DmRow dmRow) { DmRow destinationRow = null; // Problem during operation // Getting the row involved in the conflict var dmTableSelected = GetRow(dmRow); ConflictType dbConflictType = ConflictType.ErrorsOccurred; // Can't find the row on the local datastore if (dmTableSelected.Rows.Count == 0) { var errorMessage = "Change Application failed due to Row not Found on the server"; Logger.Current.Error($"Conflict detected with error: {errorMessage}"); if (applyType == DmRowState.Added) { dbConflictType = ConflictType.LocalNoRowRemoteInsert; } else if (applyType == DmRowState.Modified) { dbConflictType = ConflictType.LocalNoRowRemoteUpdate; } else if (applyType == DmRowState.Deleted) { dbConflictType = ConflictType.LocalNoRowRemoteDelete; } } else { // We have a problem and found the row on the server side destinationRow = dmTableSelected.Rows[0]; var isTombstone = (bool)destinationRow["sync_row_is_tombstone"]; // the row on local is deleted if (isTombstone) { if (applyType == DmRowState.Added) { dbConflictType = ConflictType.LocalDeleteRemoteInsert; } else if (applyType == DmRowState.Modified) { dbConflictType = ConflictType.LocalDeleteRemoteUpdate; } else if (applyType == DmRowState.Deleted) { dbConflictType = ConflictType.LocalDeleteRemoteDelete; } } else { var isLocallyCreated = destinationRow["create_scope_id"] == DBNull.Value; var islocallyUpdated = destinationRow["update_scope_id"] == DBNull.Value; if (applyType == DmRowState.Added && islocallyUpdated) { dbConflictType = ConflictType.LocalUpdateRemoteInsert; } else if (applyType == DmRowState.Added && isLocallyCreated) { dbConflictType = ConflictType.LocalInsertRemoteInsert; } else if (applyType == DmRowState.Modified && islocallyUpdated) { dbConflictType = ConflictType.LocalUpdateRemoteUpdate; } else if (applyType == DmRowState.Modified && isLocallyCreated) { dbConflictType = ConflictType.LocalInsertRemoteUpdate; } else if (applyType == DmRowState.Deleted && islocallyUpdated) { dbConflictType = ConflictType.LocalUpdateRemoteDelete; } else if (applyType == DmRowState.Deleted && isLocallyCreated) { dbConflictType = ConflictType.LocalInsertRemoteDelete; } } } // Generate the conflict var conflict = new SyncConflict(dbConflictType); conflict.AddRemoteRow(dmRow); if (destinationRow != null) { conflict.AddLocalRow(destinationRow); } dmTableSelected.Clear(); return(conflict); }
public void AddSerializedConflict(SyncConflict conflict, IsolatedStorageOfflineContext context) { }
/// <summary> /// Handler for the ApplyChangedFailed event of the SqlSyncProvider class. This is used to record /// conflict information and apply the service conflict resolution policy. /// </summary> /// <param name="sender">Sender object</param> /// <param name="e">Event args</param> private void SqlSyncProviderApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e) { ApplyAction applyAction = ApplyAction.Continue; // Note: LocalChange table name may be null if the record does not exist on the server. So use the remote table name. string tableName = e.Conflict.RemoteChange.TableName; Type entityType = _configuration.TableGlobalNameToTypeMapping[tableName]; ConstructorInfo constructorInfo = entityType.GetConstructor(Type.EmptyTypes); // Handle Errors first if (null != e.Error) { var syncError = new SyncError { LiveEntity = (IOfflineEntity)constructorInfo.Invoke(null), ErrorEntity = (IOfflineEntity)constructorInfo.Invoke(null) }; //Note: When Error is not null, the conflict type should be ErrorsOccurred. Assert, just to make sure this is always correct. Debug.Assert(e.Conflict.Type == DbConflictType.ErrorsOccurred, "Conflict.Type is not ErrorsOccurred."); syncError.Description = e.Error.Message; // Fill in the error entity. This is the value of the client entity. _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], syncError.ErrorEntity); // Mark the error entity as a tombstone, if the client sent a delete. // Note: The DataRow.RowState property is not marked as deleted, so we cannot use that // to determine if the client change was a delete. if (e.Conflict.Stage == DbSyncStage.ApplyingDeletes) { syncError.ErrorEntity.ServiceMetadata.IsTombstone = true; } // Get the current version from the server. IOfflineEntity serverVersion = GetCurrentServerVersionForEntities( new List<IOfflineEntity> { syncError.ErrorEntity }, (SqlConnection)e.Connection, (SqlTransaction)e.Transaction) .FirstOrDefault(); // There is no item corresponding to the item sent from the client. // Example is an INSERT which caused an RI error and server does not have the record. if (null == serverVersion) { // If there is no server record and the client changes is not a tombstone, then // set the LiveEntity as a compensating action which is a tombstone. // This means that the client has to apply the delete locally // for data convergence. // If there is no server record and the client is a tombstone, then we ideally should // just ackowledge the action and don't send any response but // for now we will keep the sync error as is. _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], syncError.LiveEntity); syncError.LiveEntity.ServiceMetadata.IsTombstone = true; } else { syncError.LiveEntity = serverVersion; } if (this.ApplyClientChangeFailed != null) { this.ApplyClientChangeFailed(syncError.ErrorEntity); } // This will add the item as an exception to the server knowledge and will also send the exception to the client. // However the exception will be cleared the next time the client uploads changes, since we increment tickcounts always. applyAction = ApplyAction.Continue; // The ApplyChangesFailed event is fired when a conflict is detected. During resolution, // if change application fails due to some errors (such as RI, connectivity issues etc), // the provider fires the ApplyChangesFailed event again // to report an error. If we save the error entity as is, then both the original conflict and this new error // will be sent back to the client in the response. Since this is not desirable, we first need to remove the // corresponding conflict entity from the _conflicts collection before recording the error. // Note: item versions are not bumped since the conflict has not yet been resolved. RemoveEntityFromConflictCollection(tableName, syncError.LiveEntity); _syncErrors.Add(syncError); e.Action = applyAction; return; } // Create instances of OfflineCapableEntities and initialize the WinningChange and LosingChange properties. var c = new SyncConflict { LiveEntity = (IOfflineEntity)constructorInfo.Invoke(null), LosingEntity = (IOfflineEntity)constructorInfo.Invoke(null) }; ConflictResolutionPolicy policyToUse = _conflictResolutionPolicy; SyncConflictResolution? userResolution = null; // Check and fire any Conflict interceptors if (_configuration.HasConflictInterceptors(this._scopeName) || _configuration.HasTypedConflictInterceptor(this._scopeName, entityType)) { userResolution = GetUserConflictResolution(e, constructorInfo, entityType); if (userResolution != null && userResolution == SyncConflictResolution.ServerWins) { policyToUse = ConflictResolutionPolicy.ServerWins; } else if (userResolution != null && (userResolution == SyncConflictResolution.ClientWins || userResolution == SyncConflictResolution.Merge)) { // If resolution is Merge or ClientWins, set the resolution to ClientWins so the runtime will // retry with force write and save the merged values back policyToUse = ConflictResolutionPolicy.ClientWins; } } // If there were no Errors, then act based on the service conflict resolution policy. switch (policyToUse) { // ServerWins policy... case ConflictResolutionPolicy.ServerWins: // For OCS, ApplyAction.Continue means ServerWins (local change will be maintained). applyAction = ApplyAction.Continue; // If the local change exists, then save it in the WinningChange property if (null != e.Conflict.LocalChange && 1 == e.Conflict.LocalChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.LocalChange.Columns, e.Conflict.LocalChange.Rows[0], c.LiveEntity); } // If local change does not exist else { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], c.LiveEntity); c.LiveEntity.ServiceMetadata.IsTombstone = true; } // Save the remote change in the LosingChange property. if (1 == e.Conflict.RemoteChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], c.LosingEntity); } // Save the conflict resolution policy. c.Resolution = WebUtil.GetSyncConflictResolution(ConflictResolutionPolicy.ServerWins); // Set the tombstone flag based on the type of the conflict. switch (e.Conflict.Type) { case DbConflictType.LocalDeleteRemoteDelete: c.LosingEntity.ServiceMetadata.IsTombstone = true; c.LiveEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalDeleteRemoteUpdate: c.LiveEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalUpdateRemoteDelete: c.LosingEntity.ServiceMetadata.IsTombstone = true; break; // No changes to the tombstone flag for other cases. default: break; } if (this.ApplyClientChangeFailed != null) { this.ApplyClientChangeFailed(c.LosingEntity); } break; // ClientWins policy... case ConflictResolutionPolicy.ClientWins: // For OCS, client change can be kept by using ApplyAction.RetryWithForceWrite. applyAction = ApplyAction.RetryWithForceWrite; if (1 == e.Conflict.RemoteChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.RemoteChange.Rows[0], c.LiveEntity); } // If the local change exists, then save it in the WinningChange property if (1 == e.Conflict.LocalChange.Rows.Count) { _converter.GetEntityFromDataRow(e.Conflict.RemoteChange.Columns, e.Conflict.LocalChange.Rows[0], c.LosingEntity); } // Save the conflict resolution policy. c.Resolution = userResolution ?? WebUtil.GetSyncConflictResolution(ConflictResolutionPolicy.ClientWins); // Set the tombstone flag based on the type of the conflict. switch (e.Conflict.Type) { case DbConflictType.LocalDeleteRemoteDelete: c.LosingEntity.ServiceMetadata.IsTombstone = true; c.LiveEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalDeleteRemoteUpdate: c.LosingEntity.ServiceMetadata.IsTombstone = true; break; case DbConflictType.LocalUpdateRemoteDelete: c.LiveEntity.ServiceMetadata.IsTombstone = true; break; // No changes to the tombstone flag for other cases. default: break; } if (this.ApplyClientChangeFailed != null) { this.ApplyClientChangeFailed(c.LiveEntity); } break; } // After deciding on the Live and the Losing entities for the conflict, we need to generate and save the SyncId // of the LiveEntity. This value is used later after all changes are applied to project on the latest // server knowledge and add positive exceptions to the updated client knowledge that is sent in the response. SyncId rowId = GenerateSyncIdForConflictingEntity(tableName, c.LiveEntity); if (!_conflictToSyncEntityIdMapping.ContainsKey(c)) { _conflictToSyncEntityIdMapping.Add(c, rowId); // Note: SyncId's are unique for each entity. Debug.Assert(!_syncEntityIdToConflictMapping.ContainsKey(rowId), "!_syncEntityIdToConflictMapping.ContainsKey(rowId)"); // Also fill the reverse mapping of syncId to the conflict entity. _syncEntityIdToConflictMapping.Add(rowId, c); } _conflicts.Add(c); e.Action = applyAction; }
public void AddSerializedConflict(SyncConflict conflict, IsolatedStorageOfflineContext context) { Type entityType = conflict.LiveEntity.GetType(); IsolatedStorageOfflineEntity entity = Collections[entityType].AddOrUpdateSyncEntity((IsolatedStorageOfflineEntity)conflict.LiveEntity); IsolatedStorageSyncConflict oldConflict = (IsolatedStorageSyncConflict)Collections[entityType].MapSyncConflict(entity, conflict, context); SyncConflicts.Add(conflict); if (oldConflict != null) { ClearSyncConflict(oldConflict,context); } }