/// <summary> /// A conflict has occured, we try to ask for the solution to the user /// </summary> internal (ApplyAction, DmRow) GetConflictAction(SyncConflict conflict, ConflictResolutionPolicy policy, DbConnection connection, DbTransaction transaction = null) { ConflictAction conflictAction = ConflictAction.ServerWins; if (policy == ConflictResolutionPolicy.ClientWins) { conflictAction = ConflictAction.ClientWins; } var arg = new ApplyChangeFailedEventArgs(conflict, conflictAction, connection, transaction); this.ApplyChangedFailed?.Invoke(this, arg); // if ConflictAction is ServerWins or MergeRow it's Ok to set to Continue ApplyAction action = ApplyAction.Continue; if (arg.Action == ConflictAction.ClientWins) { action = ApplyAction.RetryWithForceWrite; } DmRow finalRow = arg.Action == ConflictAction.MergeRow ? arg.FinalRow : null; // returning the action to take, and actually the finalRow if action is set to Merge return(action, finalRow); }
public ApplyChangeFailedEventArgs(SyncConflict dbSyncConflict, ApplyAction action, DbConnection connection, DbTransaction transaction) { this._syncConflict = dbSyncConflict; this._connection = connection; this._transaction = transaction; this._applyAction = action; }
/// <summary> /// Try to apply changes on the server. /// Internally will call ApplyUpdate or ApplyDelete and will return /// </summary> /// <param name="changes">Changes</param> /// <returns>every lines not updated / deleted in the destination data source</returns> internal async Task <int> ApplyChangesAsync(Guid localScopeId, Guid senderScopeId, SyncTable changesTable, long lastTimestamp, List <SyncConflict> conflicts) { int appliedRows = 0; foreach (var row in changesTable.Rows) { bool operationComplete = false; try { if (ApplyType == DataRowState.Modified) { operationComplete = await this.ApplyUpdateAsync(row, lastTimestamp, senderScopeId, false).ConfigureAwait(false); } else if (ApplyType == DataRowState.Deleted) { operationComplete = await this.ApplyDeleteAsync(row, lastTimestamp, senderScopeId, false).ConfigureAwait(false); } if (operationComplete) { appliedRows++; } else { conflicts.Add(GetConflict(row, await GetRowAsync(localScopeId, row, changesTable).ConfigureAwait(false))); } } catch (Exception ex) { if (this.IsUniqueKeyViolation(ex)) { // Generate the conflict var conflict = new SyncConflict(ConflictType.UniqueKeyConstraint); // Add the row as Remote row conflict.AddRemoteRow(row); // Get the local row var localRow = await GetRowAsync(localScopeId, row, changesTable).ConfigureAwait(false); if (localRow != null) { conflict.AddLocalRow(localRow); } conflicts.Add(conflict); } else { throw; } } } return(appliedRows); }
public ApplyChangeFailedEventArgs(SyncConflict dbSyncConflict, ConflictAction action, DbConnection connection, DbTransaction transaction) { this.syncConflict = dbSyncConflict; this.connection = connection; this.transaction = transaction; this.applyAction = action; this.finalRowTable = dbSyncConflict.RemoteRow.Table.Clone(); this.finalRowTable.TableName = dbSyncConflict.RemoteRow.Table.TableName; }
/// <summary> /// We have a conflict, try to get the source row and generate a conflict /// </summary> private SyncConflict GetConflict(SyncRow remoteConflictRow, SyncRow localConflictRow) { var dbConflictType = ConflictType.ErrorsOccurred; if (remoteConflictRow == null) { throw new UnknownException("THAT can't happen..."); } // local row is null if (localConflictRow == null && remoteConflictRow.RowState == DataRowState.Modified) { dbConflictType = ConflictType.RemoteExistsLocalNotExists; } else if (localConflictRow == null && remoteConflictRow.RowState == DataRowState.Deleted) { dbConflictType = ConflictType.RemoteIsDeletedLocalNotExists; } //// remote row is null. Can't happen //else if (remoteConflictRow == null && localConflictRow.RowState == DataRowState.Modified) // dbConflictType = ConflictType.RemoteNotExistsLocalExists; //else if (remoteConflictRow == null && localConflictRow.RowState == DataRowState.Deleted) // dbConflictType = ConflictType.RemoteNotExistsLocalIsDeleted; else if (remoteConflictRow.RowState == DataRowState.Deleted && localConflictRow.RowState == DataRowState.Deleted) { dbConflictType = ConflictType.RemoteIsDeletedLocalIsDeleted; } else if (remoteConflictRow.RowState == DataRowState.Modified && localConflictRow.RowState == DataRowState.Deleted) { dbConflictType = ConflictType.RemoteExistsLocalIsDeleted; } else if (remoteConflictRow.RowState == DataRowState.Deleted && localConflictRow.RowState == DataRowState.Modified) { dbConflictType = ConflictType.RemoteIsDeletedLocalExists; } else if (remoteConflictRow.RowState == DataRowState.Modified && localConflictRow.RowState == DataRowState.Modified) { dbConflictType = ConflictType.RemoteExistsLocalExists; } // Generate the conflict var conflict = new SyncConflict(dbConflictType); conflict.AddRemoteRow(remoteConflictRow); if (localConflictRow != null) { conflict.AddLocalRow(localConflictRow); } return(conflict); }
/// <summary> /// We have a conflict, try to get the source row and generate a conflict /// </summary> private SyncConflict GetConflict(SyncRow remoteConflictRow, SyncRow localConflictRow) { var dbConflictType = ConflictType.ErrorsOccurred; // Can't find the row on the server datastore if (localConflictRow == null) { if (ApplyType == DataRowState.Modified) { dbConflictType = ConflictType.RemoteExistsLocalNotExists; } else if (ApplyType == DataRowState.Deleted) { dbConflictType = ConflictType.RemoteIsDeletedLocalNotExists; } } else { // the row on local is deleted if (localConflictRow.RowState == DataRowState.Deleted) { if (ApplyType == DataRowState.Modified) { dbConflictType = ConflictType.RemoteExistsLocalIsDeleted; } else if (ApplyType == DataRowState.Deleted) { dbConflictType = ConflictType.RemoteIsDeletedLocalIsDeleted; } } else { dbConflictType = ConflictType.RemoteExistsLocalExists; } } // Generate the conflict var conflict = new SyncConflict(dbConflictType); conflict.AddRemoteRow(remoteConflictRow); if (localConflictRow != null) { conflict.AddLocalRow(localConflictRow); } return(conflict); }
/// <summary> /// A conflict has occured, we try to ask for the solution to the user /// </summary> internal async Task <(ApplyAction, SyncRow)> GetConflictActionAsync(SyncContext context, SyncConflict conflict, ConflictResolutionPolicy policy, DbConnection connection, DbTransaction transaction = null, CancellationToken cancellationToken = default) { var conflictAction = ConflictResolution.ServerWins; if (policy == ConflictResolutionPolicy.ClientWins) { conflictAction = ConflictResolution.ClientWins; } // Interceptor var arg = new ApplyChangesFailedArgs(context, conflict, conflictAction, connection, transaction); this.Orchestrator.logger.LogDebug(SyncEventsId.ResolveConflicts, arg); await this.Orchestrator.InterceptAsync(arg, cancellationToken).ConfigureAwait(false); // if ConflictAction is ServerWins or MergeRow it's Ok to set to Continue var action = ApplyAction.Continue; // Change action only if we choose ClientWins or Rollback. // for ServerWins or MergeRow, action is Continue if (arg.Resolution == ConflictResolution.ClientWins) { action = ApplyAction.RetryWithForceWrite; } else if (arg.Resolution == ConflictResolution.Rollback) { action = ApplyAction.Rollback; } var finalRow = arg.Resolution == ConflictResolution.MergeRow ? arg.FinalRow : null; // returning the action to take, and actually the finalRow if action is set to Merge return(action, finalRow); }
/// <summary> /// Handle a conflict /// The int returned is the conflict count I need /// </summary> /// changeApplicationAction, conflictCount, resolvedRow, conflictApplyInt internal async Task <(int conflictResolvedCount, SyncRow resolvedRow, int rowAppliedCount)> HandleConflictAsync( Guid localScopeId, Guid senderScopeId, DbSyncAdapter syncAdapter, SyncContext context, SyncConflict conflict, ConflictResolutionPolicy policy, long lastTimestamp, DbConnection connection, DbTransaction transaction) { SyncRow finalRow; ApplyAction conflictApplyAction; int rowAppliedCount = 0; (conflictApplyAction, finalRow) = await this.GetConflictActionAsync(context, conflict, policy, connection, transaction).ConfigureAwait(false); // Conflict rollbacked by user if (conflictApplyAction == ApplyAction.Rollback) { throw new RollbackException("Rollback action taken on conflict"); } // Local provider wins, update metadata if (conflictApplyAction == ApplyAction.Continue) { var isMergeAction = finalRow != null; var row = isMergeAction ? finalRow : conflict.LocalRow; // Conflict on a line that is not present on the datasource if (row == null) { return(0, finalRow, 0); } // if we have a merge action, we apply the row on the server if (isMergeAction) { // if merge, we update locally the row and let the update_scope_id set to null var isUpdated = await syncAdapter.ApplyUpdateAsync(row, lastTimestamp, null, true); // We don't update metadatas so the row is updated (on server side) // and is mark as updated locally. // and will be returned back to sender, since it's a merge, and we need it on the client if (!isUpdated) { throw new Exception("Can't update the merge row."); } } finalRow = isMergeAction ? row : conflict.LocalRow; // We don't do anything, since we let the original row. so we resolved one conflict but applied no rows return(conflictResolvedCount : 1, finalRow, rowAppliedCount : 0); } // We gonna apply with force the line if (conflictApplyAction == ApplyAction.RetryWithForceWrite) { // TODO : Should Raise an error ? if (conflict.RemoteRow == null) { return(0, finalRow, 0); } bool operationComplete = false; switch (conflict.Type) { // Remote source has row, Local don't have the row, so insert it case ConflictType.RemoteExistsLocalExists: case ConflictType.RemoteExistsLocalNotExists: case ConflictType.RemoteExistsLocalIsDeleted: case ConflictType.UniqueKeyConstraint: operationComplete = await syncAdapter.ApplyUpdateAsync(conflict.RemoteRow, lastTimestamp, senderScopeId, true); rowAppliedCount = 1; break; // Conflict, but both have delete the row, so nothing to do case ConflictType.RemoteIsDeletedLocalIsDeleted: case ConflictType.RemoteIsDeletedLocalNotExists: operationComplete = true; rowAppliedCount = 0; break; // The remote has delete the row, and local has insert or update it // So delete the local row case ConflictType.RemoteIsDeletedLocalExists: operationComplete = await syncAdapter.ApplyDeleteAsync(conflict.RemoteRow, lastTimestamp, senderScopeId, true); rowAppliedCount = 1; break; case ConflictType.RemoteCleanedupDeleteLocalUpdate: case ConflictType.ErrorsOccurred: return(0, finalRow, 0); } finalRow = conflict.RemoteRow; //After a force update, there is a problem, so raise exception if (!operationComplete) { finalRow = null; return(0, finalRow, rowAppliedCount); } return(1, finalRow, rowAppliedCount); } return(0, finalRow, 0); }
/// <summary> /// Handle a conflict /// The int returned is the conflict count I need /// </summary> internal async Task <(ChangeApplicationAction, int, DmRow)> HandleConflictAsync(DbSyncAdapter syncAdapter, SyncContext context, SyncConflict conflict, ConflictResolutionPolicy policy, ScopeInfo scope, long fromScopeLocalTimeStamp, DbConnection connection, DbTransaction transaction) { DmRow finalRow = null; var conflictApplyAction = ApplyAction.Continue; (conflictApplyAction, finalRow) = await this.GetConflictActionAsync(context, conflict, policy, connection, transaction); // Default behavior and an error occured if (conflictApplyAction == ApplyAction.Rollback) { conflict.ErrorMessage = "Rollback action taken on conflict"; conflict.Type = ConflictType.ErrorsOccurred; return(ChangeApplicationAction.Rollback, 0, null); } // Local provider wins, update metadata if (conflictApplyAction == ApplyAction.Continue) { var isMergeAction = finalRow != null; var row = isMergeAction ? finalRow : conflict.LocalRow; // Conflict on a line that is not present on the datasource if (row == null) { return(ChangeApplicationAction.Continue, 0, finalRow); } if (row != null) { // if we have a merge action, we apply the row on the server if (isMergeAction) { bool isUpdated = false; bool isInserted = false; // Insert metadata is a merge, actually var commandType = DbCommandType.UpdateMetadata; isUpdated = syncAdapter.ApplyUpdate(row, scope, true); if (!isUpdated) { // Insert the row isInserted = syncAdapter.ApplyInsert(row, scope, true); // Then update the row to mark this row as updated from server // and get it back to client isUpdated = syncAdapter.ApplyUpdate(row, scope, true); commandType = DbCommandType.InsertMetadata; } if (!isUpdated && !isInserted) { throw new Exception("Can't update the merge row."); } // IF we have insert the row in the server side, to resolve the conflict // Whe should update the metadatas correctly if (isUpdated || isInserted) { using (var metadataCommand = syncAdapter.GetCommand(commandType)) { // getting the row updated from server var dmTableRow = syncAdapter.GetRow(row); row = dmTableRow.Rows[0]; // Deriving Parameters syncAdapter.SetCommandParameters(commandType, metadataCommand); // Set the id parameter syncAdapter.SetColumnParametersValues(metadataCommand, row); var version = row.RowState == DmRowState.Deleted ? DmRowVersion.Original : DmRowVersion.Current; Guid?create_scope_id = row["create_scope_id"] != DBNull.Value ? (Guid?)row["create_scope_id"] : null; long createTimestamp = row["create_timestamp", version] != DBNull.Value ? Convert.ToInt64(row["create_timestamp", version]) : 0; // The trick is to force the row to be "created before last sync" // Even if we just inserted it // to be able to get the row in state Updated (and not Added) row["create_scope_id"] = create_scope_id; row["create_timestamp"] = fromScopeLocalTimeStamp - 1; // Update scope id is set to server side Guid?update_scope_id = row["update_scope_id"] != DBNull.Value ? (Guid?)row["update_scope_id"] : null; long updateTimestamp = row["update_timestamp", version] != DBNull.Value ? Convert.ToInt64(row["update_timestamp", version]) : 0; row["update_scope_id"] = null; row["update_timestamp"] = updateTimestamp; // apply local row, set scope.id to null becoz applied locally var rowsApplied = syncAdapter.InsertOrUpdateMetadatas(metadataCommand, row, null); if (!rowsApplied) { throw new Exception("No metadatas rows found, can't update the server side"); } } } } finalRow = isMergeAction ? row : conflict.LocalRow; // We don't do anything on the local provider, so we do not need to return a +1 on syncConflicts count return(ChangeApplicationAction.Continue, 0, finalRow); } return(ChangeApplicationAction.Rollback, 0, finalRow); } // We gonna apply with force the line if (conflictApplyAction == ApplyAction.RetryWithForceWrite) { if (conflict.RemoteRow == null) { // TODO : Should Raise an error ? return(ChangeApplicationAction.Rollback, 0, finalRow); } bool operationComplete = false; // create a localscope to override values var localScope = new ScopeInfo { Name = scope.Name, Timestamp = fromScopeLocalTimeStamp }; DbCommandType commandType = DbCommandType.InsertMetadata; bool needToUpdateMetadata = true; switch (conflict.Type) { // Remote source has row, Local don't have the row, so insert it case ConflictType.RemoteUpdateLocalNoRow: case ConflictType.RemoteInsertLocalNoRow: operationComplete = syncAdapter.ApplyInsert(conflict.RemoteRow, localScope, true); commandType = DbCommandType.InsertMetadata; break; // Conflict, but both have delete the row, so nothing to do case ConflictType.RemoteDeleteLocalDelete: case ConflictType.RemoteDeleteLocalNoRow: operationComplete = true; needToUpdateMetadata = false; break; // The remote has delete the row, and local has insert or update it // So delete the local row case ConflictType.RemoteDeleteLocalUpdate: case ConflictType.RemoteDeleteLocalInsert: operationComplete = syncAdapter.ApplyDelete(conflict.RemoteRow, localScope, true); commandType = DbCommandType.UpdateMetadata; break; // Remote insert and local delete, sor insert again on local // but tracking line exist, so make an update on metadata case ConflictType.RemoteInsertLocalDelete: case ConflictType.RemoteUpdateLocalDelete: operationComplete = syncAdapter.ApplyInsert(conflict.RemoteRow, localScope, true); commandType = DbCommandType.UpdateMetadata; break; // Remote insert and local insert/ update, take the remote row and update the local row case ConflictType.RemoteUpdateLocalInsert: case ConflictType.RemoteUpdateLocalUpdate: case ConflictType.RemoteInsertLocalInsert: case ConflictType.RemoteInsertLocalUpdate: operationComplete = syncAdapter.ApplyUpdate(conflict.RemoteRow, localScope, true); commandType = DbCommandType.UpdateMetadata; break; case ConflictType.RemoteCleanedupDeleteLocalUpdate: case ConflictType.ErrorsOccurred: return(ChangeApplicationAction.Rollback, 0, finalRow); } if (needToUpdateMetadata) { using (var metadataCommand = syncAdapter.GetCommand(commandType)) { // Deriving Parameters syncAdapter.SetCommandParameters(commandType, metadataCommand); // force applying client row, so apply scope.id (client scope here) var rowsApplied = syncAdapter.InsertOrUpdateMetadatas(metadataCommand, conflict.RemoteRow, scope.Id); if (!rowsApplied) { throw new Exception("No metadatas rows found, can't update the server side"); } } } finalRow = conflict.RemoteRow; //After a force update, there is a problem, so raise exception if (!operationComplete) { var ex = $"Can't force operation for applyType {syncAdapter.ApplyType}"; finalRow = null; return(ChangeApplicationAction.Continue, 0, finalRow); } // tableProgress.ChangesApplied += 1; return(ChangeApplicationAction.Continue, 1, finalRow); } return(ChangeApplicationAction.Rollback, 0, finalRow); }
/// <summary> /// A conflict has occured, we try to ask for the solution to the user /// </summary> internal async Task <(ApplyAction, DmRow)> GetConflictActionAsync(SyncContext context, SyncConflict conflict, ConflictResolutionPolicy policy, DbConnection connection, DbTransaction transaction = null) { var conflictAction = ConflictResolution.ServerWins; if (policy == ConflictResolutionPolicy.ClientWins) { conflictAction = ConflictResolution.ClientWins; } // Interceptor var arg = new ApplyChangesFailedArgs(context, conflict, conflictAction, connection, transaction); await this.InterceptAsync(arg); // if ConflictAction is ServerWins or MergeRow it's Ok to set to Continue var action = ApplyAction.Continue; if (arg.Resolution == ConflictResolution.ClientWins) { action = ApplyAction.RetryWithForceWrite; } var finalRow = arg.Resolution == ConflictResolution.MergeRow ? arg.FinalRow : null; // returning the action to take, and actually the finalRow if action is set to Merge return(action, finalRow); }
/// <summary> /// We have a conflict, try to get the source row and generate a conflict /// </summary> private SyncConflict GetConflict(DmRow dmRow) { DmRow localRow = null; // Problem during operation // Getting the row involved in the conflict var localTable = GetRow(dmRow); ConflictType dbConflictType = ConflictType.ErrorsOccurred; // Can't find the row on the server datastore if (localTable.Rows.Count == 0) { if (ApplyType == DmRowState.Added) { dbConflictType = ConflictType.RemoteInsertLocalNoRow; } else if (ApplyType == DmRowState.Modified) { dbConflictType = ConflictType.RemoteUpdateLocalNoRow; } else if (ApplyType == DmRowState.Deleted) { dbConflictType = ConflictType.RemoteDeleteLocalNoRow; } } else { // We have a problem and found the row on the server side localRow = localTable.Rows[0]; var isTombstone = Convert.ToBoolean(localRow["sync_row_is_tombstone"]); // the row on local is deleted if (isTombstone) { if (ApplyType == DmRowState.Added) { dbConflictType = ConflictType.RemoteInsertLocalDelete; } else if (ApplyType == DmRowState.Modified) { dbConflictType = ConflictType.RemoteUpdateLocalDelete; } else if (ApplyType == DmRowState.Deleted) { dbConflictType = ConflictType.RemoteDeleteLocalDelete; } } else { var createTimestamp = localRow["create_timestamp"] != DBNull.Value ? (long)localRow["create_timestamp"] : 0L; var updateTimestamp = localRow["update_timestamp"] != DBNull.Value ? (long)localRow["update_timestamp"] : 0L; switch (ApplyType) { case DmRowState.Added: dbConflictType = updateTimestamp == 0 ? ConflictType.RemoteInsertLocalInsert : ConflictType.RemoteInsertLocalUpdate; break; case DmRowState.Modified: dbConflictType = updateTimestamp == 0 ? ConflictType.RemoteUpdateLocalInsert : ConflictType.RemoteUpdateLocalUpdate; break; case DmRowState.Deleted: dbConflictType = updateTimestamp == 0 ? ConflictType.RemoteDeleteLocalInsert : ConflictType.RemoteDeleteLocalUpdate; break; } } } // Generate the conflict var conflict = new SyncConflict(dbConflictType); conflict.AddRemoteRow(dmRow); if (localRow != null) { conflict.AddLocalRow(localRow); } localTable.Clear(); return(conflict); }
/// <summary> /// Try to apply changes on the server. /// Internally will call ApplyInsert / ApplyUpdate or ApplyDelete /// </summary> /// <param name="dmChanges">Changes from remote</param> /// <returns>every lines not updated on the server side</returns> internal int ApplyChanges(DmView dmChanges, ScopeInfo scope, List <SyncConflict> conflicts) { int appliedRows = 0; foreach (var dmRow in dmChanges) { bool operationComplete = false; try { if (ApplyType == DmRowState.Added) { operationComplete = this.ApplyInsert(dmRow, scope, false); if (operationComplete) { UpdateMetadatas(DbCommandType.InsertMetadata, dmRow, scope); } } else if (ApplyType == DmRowState.Modified) { operationComplete = this.ApplyUpdate(dmRow, scope, false); if (operationComplete) { UpdateMetadatas(DbCommandType.UpdateMetadata, dmRow, scope); } } else if (ApplyType == DmRowState.Deleted) { operationComplete = this.ApplyDelete(dmRow, scope, false); if (operationComplete) { UpdateMetadatas(DbCommandType.UpdateMetadata, dmRow, scope); } } if (operationComplete) { // if no pb, increment then go to next row appliedRows++; } else { // Generate a conflict and add it conflicts.Add(GetConflict(dmRow)); } } catch (Exception ex) { if (this.IsUniqueKeyViolation(ex)) { // Generate the conflict var conflict = new SyncConflict(ConflictType.UniqueKeyConstraint); // Add the row as Remote row conflict.AddRemoteRow(dmRow); // Get the local row var localTable = GetRow(dmRow); if (localTable.Rows.Count > 0) { conflict.AddLocalRow(localTable.Rows[0]); } conflicts.Add(conflict); localTable.Clear(); } else { throw; } } } return(appliedRows); }
/// <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) { Debug.WriteLine("Rollback action taken on conflict"); conflict.ErrorMessage = "Rollback action taken on conflict"; conflict.Type = ConflictType.ErrorsOccurred; return(ChangeApplicationAction.Rollback); } if (ConflictApplyAction == ApplyAction.Continue) { Debug.WriteLine("Local Wins, update metadata"); // COnflict on a line that is not present on the datasource if (conflict.LocalChanges == null || conflict.LocalChanges.Rows == null || conflict.LocalChanges.Rows.Count == 0) { return(ChangeApplicationAction.Continue); } if (conflict.LocalChanges != null && conflict.LocalChanges.Rows != null && conflict.LocalChanges.Rows.Count > 0) { var localRow = conflict.LocalChanges.Rows[0]; // TODO : Différencier le timestamp de mise à jour ou de création var updateMetadataCommand = GetCommand(DbCommandType.UpdateMetadata); // Deriving Parameters this.SetCommandParameters(DbCommandType.UpdateMetadata, updateMetadataCommand); // apply local row, set scope.id to null becoz applied locally var rowsApplied = this.InsertOrUpdateMetadatas(updateMetadataCommand, localRow, null); if (!rowsApplied) { throw new Exception("No metadatas rows found, can't update the server side"); } finalRow = localRow; return(ChangeApplicationAction.Continue); } return(ChangeApplicationAction.Rollback); } // We gonna apply with force the line if (ConflictApplyAction == ApplyAction.RetryWithForceWrite) { if (conflict.RemoteChanges.Rows.Count == 0) { Debug.WriteLine("Cant find a remote row"); return(ChangeApplicationAction.Rollback); } var row = conflict.RemoteChanges.Rows[0]; bool operationComplete = false; // create a localscope to override values var localScope = new ScopeInfo { Name = scope.Name, LastTimestamp = timestamp }; DbCommandType commandType = DbCommandType.InsertMetadata; switch (conflict.Type) { // Remote source has row, Local don't have the row, so insert it case ConflictType.RemoteUpdateLocalNoRow: case ConflictType.RemoteInsertLocalNoRow: operationComplete = this.ApplyInsert(row, localScope, true); commandType = DbCommandType.InsertMetadata; break; // Conflict, but both have delete the row, so nothing to do case ConflictType.RemoteDeleteLocalDelete: case ConflictType.RemoteDeleteLocalNoRow: operationComplete = true; break; // The remote has delete the row, and local has insert or update it // So delete the local row case ConflictType.RemoteDeleteLocalUpdate: case ConflictType.RemoteDeleteLocalInsert: operationComplete = this.ApplyDelete(row, localScope, true); commandType = DbCommandType.UpdateMetadata; break; // Remote insert and local delete, sor insert again on local // but tracking line exist, so make an update on metadata case ConflictType.RemoteInsertLocalDelete: case ConflictType.RemoteUpdateLocalDelete: operationComplete = this.ApplyInsert(row, localScope, true); commandType = DbCommandType.UpdateMetadata; break; // Remote insert and local insert/ update, take the remote row and update the local row case ConflictType.RemoteUpdateLocalInsert: case ConflictType.RemoteUpdateLocalUpdate: case ConflictType.RemoteInsertLocalInsert: case ConflictType.RemoteInsertLocalUpdate: operationComplete = this.ApplyUpdate(row, localScope, true); commandType = DbCommandType.UpdateMetadata; break; case ConflictType.RemoteCleanedupDeleteLocalUpdate: case ConflictType.ErrorsOccurred: return(ChangeApplicationAction.Rollback); } var metadataCommand = GetCommand(commandType); // Deriving Parameters this.SetCommandParameters(commandType, metadataCommand); // force applying client row, so apply scope.id (client scope here) var rowsApplied = this.InsertOrUpdateMetadatas(metadataCommand, row, scope.Id); if (!rowsApplied) { 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}"; Debug.WriteLine(ex); finalRow = null; return(ChangeApplicationAction.Continue); } // tableProgress.ChangesApplied += 1; return(ChangeApplicationAction.Continue); } return(ChangeApplicationAction.Rollback); }
public delegate(ApplyAction, DmRow) ConflictActionDelegate(SyncConflict conflict, DbConnection connection, DbTransaction transaction = null);
public delegate(ApplyAction, DmRow) ConflictActionDelegate(SyncConflict conflict, ConflictResolutionPolicy policy, DbConnection connection, DbTransaction transaction = null);