Esempio n. 1
0
        /// <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;
 }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        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);
        }
Esempio n. 6
0
        /// <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);
        }
Esempio n. 9
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);
        }
Esempio n. 10
0
        /// <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);
        }
Esempio n. 11
0
        /// <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);
        }
Esempio n. 12
0
        /// <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);
        }
Esempio n. 13
0
        /// <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);
        }
Esempio n. 14
0
 public delegate(ApplyAction, DmRow) ConflictActionDelegate(SyncConflict conflict, DbConnection connection, DbTransaction transaction = null);
Esempio n. 15
0
 public delegate(ApplyAction, DmRow) ConflictActionDelegate(SyncConflict conflict, ConflictResolutionPolicy policy, DbConnection connection, DbTransaction transaction = null);