/// <summary> /// Set command parameters value mapped to Row /// </summary> internal void SetColumnParametersValues(DbCommand command, SyncRow row) { if (row.SchemaTable == null) { throw new ArgumentException("Schema table columns does not correspond to row values"); } var schemaTable = row.SchemaTable; foreach (DbParameter parameter in command.Parameters) { if (!string.IsNullOrEmpty(parameter.SourceColumn)) { // foreach parameter, check if we have a column var column = schemaTable.Columns[parameter.SourceColumn]; if (column != null) { object value = row[column] ?? DBNull.Value; DbSyncAdapter.SetParameterValue(command, parameter.ParameterName, value); } } } // return value var syncRowCountParam = DbSyncAdapter.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { syncRowCountParam.Direction = ParameterDirection.Output; syncRowCountParam.Value = DBNull.Value; } }
/// <summary> /// Apply a single update in the current datasource. if forceWrite, override conflict situation and force the update /// </summary> internal async Task <bool> ApplyUpdateAsync(SyncRow row, long lastTimestamp, Guid?senderScopeId, bool forceWrite, DbConnection connection, DbTransaction transaction) { if (row.Table == null) { throw new ArgumentException("Schema table is not present in the row"); } var command = await this.PrepareCommandAsync(DbCommandType.UpdateRow, connection, transaction); // Set the parameters value from row this.SetColumnParametersValues(command, row); // Set the special parameters for update AddScopeParametersValues(command, senderScopeId, lastTimestamp, false, forceWrite); var rowUpdatedCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); // Check if we have a return value instead var syncRowCountParam = DbTableManagerFactory.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { rowUpdatedCount = (int)syncRowCountParam.Value; } return(rowUpdatedCount > 0); }
/// <summary> /// Set command parameters value mapped to Row /// </summary> internal void SetColumnParametersValues(DbCommand command, SyncRow row) { if (row.Table == null) { throw new ArgumentException("Schema table columns does not correspond to row values"); } var schemaTable = row.Table; foreach (DbParameter parameter in command.Parameters) { // foreach parameter, check if we have a column if (!string.IsNullOrEmpty(parameter.SourceColumn)) { var column = schemaTable.Columns.FirstOrDefault(sc => sc.ColumnName.Equals(parameter.SourceColumn, SyncGlobalization.DataSourceStringComparison)); if (column != null) { object value = row[column] ?? DBNull.Value; DbTableManagerFactory.SetParameterValue(command, parameter.ParameterName, value); } } } // return value var syncRowCountParam = DbTableManagerFactory.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { syncRowCountParam.Direction = ParameterDirection.Output; } }
/// <summary> /// Create a new row /// </summary> public SyncRow NewRow(DataRowState state = DataRowState.Unchanged) { var row = new SyncRow(this.Columns.Count) { RowState = state, Table = this }; return(row); }
/// <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> /// Apply a delete on a row /// </summary> internal async Task <bool> ApplyDeleteAsync(SyncRow row, long lastTimestamp, Guid?senderScopeId, bool forceWrite) { if (row.Table == null) { throw new ArgumentException("Schema table is not present in the row"); } using (var command = this.GetCommand(DbCommandType.DeleteRow)) { if (command == null) { throw new MissingCommandException(DbCommandType.DeleteRow.ToString()); } // Deriving Parameters await this.SetCommandParametersAsync(DbCommandType.DeleteRow, command).ConfigureAwait(false); // Set the parameters value from row this.SetColumnParametersValues(command, row); // Set the special parameters for update this.AddScopeParametersValues(command, senderScopeId, lastTimestamp, true, forceWrite); var alreadyOpened = Connection.State == ConnectionState.Open; // OPen Connection if (!alreadyOpened) { await this.Connection.OpenAsync().ConfigureAwait(false); } if (Transaction != null) { command.Transaction = Transaction; } var rowDeletedCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); // Check if we have a return value instead var syncRowCountParam = DbTableManagerFactory.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { rowDeletedCount = (int)syncRowCountParam.Value; } if (!alreadyOpened) { Connection.Close(); } return(rowDeletedCount > 0); } }
/// <summary> /// A conflict has occured, we try to ask for the solution to the user /// </summary> private async Task <(ApplyAction, ConflictType, SyncRow, SyncRow, Guid?)> GetConflictActionAsync(SyncContext context, Guid localScopeId, DbSyncAdapter syncAdapter, SyncRow conflictRow, SyncTable schemaChangesTable, ConflictResolutionPolicy policy, Guid senderScopeId, DbConnection connection, DbTransaction transaction = null, CancellationToken cancellationToken = default) { // default action var resolution = policy == ConflictResolutionPolicy.ClientWins ? ConflictResolution.ClientWins : ConflictResolution.ServerWins; // if ConflictAction is ServerWins or MergeRow it's Ok to set to Continue var action = ApplyAction.Continue; // check the interceptor var interceptor = this.interceptors.GetInterceptor <ApplyChangesFailedArgs>(); SyncRow finalRow = null; SyncRow localRow = null; Guid? finalSenderScopeId = senderScopeId; // default conflict type ConflictType conflictType = conflictRow.RowState == DataRowState.Deleted ? ConflictType.RemoteIsDeletedLocalExists : ConflictType.RemoteExistsLocalExists; // if is not empty, get the conflict and intercept if (!interceptor.IsEmpty) { // Get the localRow localRow = await this.InternalGetConflictRowAsync(context, syncAdapter, localScopeId, conflictRow, schemaChangesTable, connection, transaction).ConfigureAwait(false); // Get the conflict var conflict = this.GetConflict(conflictRow, localRow); // Interceptor var arg = new ApplyChangesFailedArgs(context, conflict, resolution, senderScopeId, connection, transaction); await this.InterceptAsync(arg, cancellationToken).ConfigureAwait(false); resolution = arg.Resolution; finalRow = arg.Resolution == ConflictResolution.MergeRow ? arg.FinalRow : null; finalSenderScopeId = arg.SenderScopeId; conflictType = arg.Conflict.Type; } // Change action only if we choose ClientWins or Rollback. // for ServerWins or MergeRow, action is Continue if (resolution == ConflictResolution.ClientWins) { action = ApplyAction.RetryWithForceWrite; } else if (resolution == ConflictResolution.Rollback) { action = ApplyAction.Rollback; } // returning the action to take, and actually the finalRow if action is set to Merge return(action, conflictType, localRow, finalRow, finalSenderScopeId); }
/// <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> /// Update a metadata row /// </summary> internal async Task <bool> UpdateMetadatasAsync(SyncRow row, Guid?senderScopeId, bool forceWrite, DbConnection connection, DbTransaction transaction) { var command = await this.PrepareCommandAsync(DbCommandType.UpdateMetadata, connection, transaction); // Set the parameters value from row this.SetColumnParametersValues(command, row); // Set the special parameters for update AddScopeParametersValues(command, senderScopeId, 0, row.RowState == DataRowState.Deleted, forceWrite); var metadataUpdatedRowsCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); // Check if we have a return value instead var syncRowCountParam = DbTableManagerFactory.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { metadataUpdatedRowsCount = (int)syncRowCountParam.Value; } return(metadataUpdatedRowsCount > 0); }
GetConflictActionAsync(IScopeInfo scopeInfo, SyncContext context, Guid localScopeId, DbSyncAdapter syncAdapter, SyncRow conflictRow, SyncTable schemaChangesTable, ConflictResolutionPolicy policy, Guid senderScopeId, DbConnection connection, DbTransaction transaction, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { // default action var resolution = policy == ConflictResolutionPolicy.ClientWins ? ConflictResolution.ClientWins : ConflictResolution.ServerWins; // if ConflictAction is ServerWins or MergeRow it's Ok to set to Continue var action = ApplyAction.Continue; // check the interceptor var interceptors = this.interceptors.GetInterceptors <ApplyChangesFailedArgs>(); SyncRow finalRow = null; SyncRow localRow = null; Guid? finalSenderScopeId = senderScopeId; // default conflict type ConflictType conflictType = conflictRow.RowState == DataRowState.Deleted ? ConflictType.RemoteIsDeletedLocalExists : ConflictType.RemoteExistsLocalExists; // if is not empty, get the conflict and intercept // We don't get the conflict on automatic conflict resolution // Since it's an automatic resolution, we don't need to get the local conflict row // So far we get the conflict only if an interceptor exists if (interceptors.Count > 0) { // Get the localRow (context, localRow) = await this.InternalGetConflictRowAsync(scopeInfo, context, syncAdapter, localScopeId, conflictRow, schemaChangesTable, connection, transaction).ConfigureAwait(false); // Get the conflict var conflict = this.GetConflict(conflictRow, localRow); // Interceptor var arg = new ApplyChangesFailedArgs(context, conflict, resolution, senderScopeId, connection, transaction); await this.InterceptAsync(arg, progress, cancellationToken).ConfigureAwait(false); resolution = arg.Resolution; finalRow = arg.Resolution == ConflictResolution.MergeRow ? arg.FinalRow : null; finalSenderScopeId = arg.SenderScopeId; conflictType = arg.Conflict.Type; } else { // Check logger, because we make some reflection here if (this.Logger.IsEnabled(LogLevel.Debug)) { var args = new { Row = conflictRow, Resolution = resolution, Connection = connection, Transaction = transaction }; this.Logger.LogDebug(new EventId(SyncEventsId.ApplyChangesFailed.Id, "ApplyChangesFailed"), args); } } // Change action only if we choose ClientWins or Rollback. // for ServerWins or MergeRow, action is Continue if (resolution == ConflictResolution.ClientWins) { action = ApplyAction.RetryWithForceWrite; } else if (resolution == ConflictResolution.Rollback) { action = ApplyAction.Rollback; } // returning the action to take, and actually the finalRow if action is set to Merge return(context, action, conflictType, localRow, finalRow, finalSenderScopeId); }
/// <summary> /// Try to get a source row /// </summary> /// <returns></returns> internal async Task <SyncRow> GetRowAsync(Guid localScopeId, SyncRow primaryKeyRow, SyncTable schema) { // Get the row in the local repository using (var selectCommand = GetCommand(DbCommandType.SelectRow)) { if (selectCommand == null) { throw new MissingCommandException(DbCommandType.SelectRow.ToString()); } // Deriving Parameters await this.SetCommandParametersAsync(DbCommandType.SelectRow, selectCommand).ConfigureAwait(false); // set the primary keys columns as parameters this.SetColumnParametersValues(selectCommand, primaryKeyRow); var alreadyOpened = Connection.State == ConnectionState.Open; // Open Connection if (!alreadyOpened) { Connection.Open(); } if (Transaction != null) { selectCommand.Transaction = Transaction; } // Create a select table based on the schema in parameter + scope columns var changesSet = schema.Schema.Clone(false); var selectTable = CreateChangesTable(schema, changesSet); SyncRow syncRow = null; using (var dataReader = await selectCommand.ExecuteReaderAsync().ConfigureAwait(false)) { while (dataReader.Read()) { // Create a new empty row syncRow = selectTable.NewRow(); for (var i = 0; i < dataReader.FieldCount; i++) { var columnName = dataReader.GetName(i); // if we have the tombstone value, do not add it to the table if (columnName == "sync_row_is_tombstone") { var isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; syncRow.RowState = isTombstone ? DataRowState.Deleted : DataRowState.Unchanged; continue; } if (columnName == "update_scope_id") { var readerScopeId = dataReader.GetValue(i); //// if update_scope_id is null, so the row owner is the local database //// if update_scope_id is not null, the row owner is someone else //if (readerScopeId == DBNull.Value || readerScopeId == null) // syncRow.UpdateScopeId = localScopeId; //else if (SyncTypeConverter.TryConvertTo<Guid>(readerScopeId, out var updateScopeIdObject)) // syncRow.UpdateScopeId = (Guid)updateScopeIdObject; //else // throw new Exception("Impossible to parse row['update_scope_id']"); continue; } var columnValueObject = dataReader.GetValue(i); var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; syncRow[columnName] = columnValue; } } } // Close Connection if (!alreadyOpened) { Connection.Close(); } if (syncRow != null) { syncRow.RowState = primaryKeyRow.RowState; } return(syncRow); } }
public RowsChangesSelectedArgs(SyncContext context, SyncRow syncRow, SyncTable schemaTable, DbConnection connection, DbTransaction transaction) : base(context, connection, transaction) { this.SyncRow = syncRow; this.SchemaTable = schemaTable; }
/// <summary> /// Handle a conflict /// The int returned is the conflict count I need /// </summary> private async Task <(int conflictResolvedCount, SyncRow resolvedRow, int rowAppliedCount)> HandleConflictAsync( Guid localScopeId, Guid senderScopeId, DbSyncAdapter syncAdapter, SyncContext context, SyncRow conflictRow, SyncTable schemaChangesTable, ConflictResolutionPolicy policy, long?lastTimestamp, DbConnection connection, DbTransaction transaction) { SyncRow finalRow; SyncRow localRow; ConflictType conflictType; ApplyAction conflictApplyAction; int rowAppliedCount = 0; Guid? nullableSenderScopeId; (conflictApplyAction, conflictType, localRow, finalRow, nullableSenderScopeId) = await this.GetConflictActionAsync(context, localScopeId, syncAdapter, conflictRow, schemaChangesTable, policy, senderScopeId, 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 : localRow; // Conflict on a line that is not present on the datasource if (row == null) { return(conflictResolvedCount : 1, finalRow, rowAppliedCount : 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 this.InternalApplyConflictUpdateAsync(context, syncAdapter, row, lastTimestamp, null, true, connection, transaction).ConfigureAwait(false); // 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 : 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 (conflictRow == null) { return(0, finalRow, 0); } bool operationComplete = false; switch (conflictType) { // Remote source has row, Local don't have the row, so insert it case ConflictType.RemoteExistsLocalExists: operationComplete = await this.InternalApplyConflictUpdateAsync(context, syncAdapter, conflictRow, lastTimestamp, nullableSenderScopeId, true, connection, transaction).ConfigureAwait(false); rowAppliedCount = 1; break; case ConflictType.RemoteExistsLocalNotExists: case ConflictType.RemoteExistsLocalIsDeleted: case ConflictType.UniqueKeyConstraint: operationComplete = await this.InternalApplyConflictUpdateAsync(context, syncAdapter, conflictRow, lastTimestamp, nullableSenderScopeId, true, connection, transaction).ConfigureAwait(false); rowAppliedCount = 1; break; // Conflict, but both have delete the row, so just update the metadata to the right winner case ConflictType.RemoteIsDeletedLocalIsDeleted: operationComplete = await this.InternalUpdateMetadatasAsync(context, syncAdapter, conflictRow, nullableSenderScopeId, true, connection, transaction).ConfigureAwait(false); rowAppliedCount = 0; break; // The row does not exists locally, and since it's coming from a deleted state, we can forget it 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 this.InternalApplyConflictDeleteAsync(context, syncAdapter, conflictRow, lastTimestamp, nullableSenderScopeId, true, connection, transaction); // Conflict, but both have delete the row, so just update the metadata to the right winner if (!operationComplete) { operationComplete = await this.InternalUpdateMetadatasAsync(context, syncAdapter, conflictRow, nullableSenderScopeId, true, connection, transaction); rowAppliedCount = 0; } else { rowAppliedCount = 1; } break; case ConflictType.ErrorsOccurred: return(0, finalRow, 0); } finalRow = conflictRow; //After a force update, there is a problem, so raise exception if (!operationComplete) { throw new UnknownException("Force update should always work.. contact the author :)"); } return(1, finalRow, rowAppliedCount); } return(0, finalRow, 0); }
/// <summary> /// Try to get a source row /// </summary> private async Task <SyncRow> InternalGetConflictRowAsync(SyncContext context, DbSyncAdapter syncAdapter, Guid localScopeId, SyncRow primaryKeyRow, SyncTable schema, DbConnection connection, DbTransaction transaction) { // Get the row in the local repository var command = await syncAdapter.GetCommandAsync(DbCommandType.SelectRow, connection, transaction); // set the primary keys columns as parameters syncAdapter.SetColumnParametersValues(command, primaryKeyRow); // Create a select table based on the schema in parameter + scope columns var changesSet = schema.Schema.Clone(false); var selectTable = DbSyncAdapter.CreateChangesTable(schema, changesSet); using var dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false); if (!dataReader.Read()) { dataReader.Close(); return(null); } // Create a new empty row var syncRow = selectTable.NewRow(); for (var i = 0; i < dataReader.FieldCount; i++) { var columnName = dataReader.GetName(i); // if we have the tombstone value, do not add it to the table if (columnName == "sync_row_is_tombstone") { var isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; syncRow.RowState = isTombstone ? DataRowState.Deleted : DataRowState.Modified; continue; } if (columnName == "update_scope_id") { // var readerScopeId = dataReader.GetValue(i); continue; } var columnValueObject = dataReader.GetValue(i); var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; syncRow[columnName] = columnValue; } // if syncRow is not a deleted row, we can check for which kind of row it is. if (syncRow != null && syncRow.RowState == DataRowState.Unchanged) { syncRow.RowState = DataRowState.Modified; } dataReader.Close(); return(syncRow); }
/// <summary> /// add a local row /// </summary> internal void AddLocalRow(SyncRow row) => this.LocalRow = row;
/// <summary> /// Try to get a source row /// </summary> /// <returns></returns> internal async Task <SyncRow> GetRowAsync(Guid localScopeId, SyncRow primaryKeyRow, SyncTable schema, DbConnection connection, DbTransaction transaction) { // Get the row in the local repository var command = await this.PrepareCommandAsync(DbCommandType.SelectRow, connection, transaction); // set the primary keys columns as parameters this.SetColumnParametersValues(command, primaryKeyRow); // Create a select table based on the schema in parameter + scope columns var changesSet = schema.Schema.Clone(false); var selectTable = CreateChangesTable(schema, changesSet); SyncRow syncRow = null; using (var dataReader = await command.ExecuteReaderAsync().ConfigureAwait(false)) { while (dataReader.Read()) { // Create a new empty row syncRow = selectTable.NewRow(); for (var i = 0; i < dataReader.FieldCount; i++) { var columnName = dataReader.GetName(i); // if we have the tombstone value, do not add it to the table if (columnName == "sync_row_is_tombstone") { var isTombstone = Convert.ToInt64(dataReader.GetValue(i)) > 0; syncRow.RowState = isTombstone ? DataRowState.Deleted : DataRowState.Modified; continue; } if (columnName == "update_scope_id") { var readerScopeId = dataReader.GetValue(i); //// if update_scope_id is null, so the row owner is the local database //// if update_scope_id is not null, the row owner is someone else //if (readerScopeId == DBNull.Value || readerScopeId == null) // syncRow.UpdateScopeId = localScopeId; //else if (SyncTypeConverter.TryConvertTo<Guid>(readerScopeId, out var updateScopeIdObject)) // syncRow.UpdateScopeId = (Guid)updateScopeIdObject; //else // throw new Exception("Impossible to parse row['update_scope_id']"); continue; } var columnValueObject = dataReader.GetValue(i); var columnValue = columnValueObject == DBNull.Value ? null : columnValueObject; syncRow[columnName] = columnValue; } } } // if syncRow is not a deleted row, we can check for which kind of row it is. if (syncRow != null && syncRow.RowState == DataRowState.Unchanged) { syncRow.RowState = DataRowState.Modified; } return(syncRow); }
/// <summary> /// add a remote row /// </summary> internal void AddRemoteRow(SyncRow row) => this.RemoteRow = row;
/// <summary> /// Apply a single update in the current datasource. if forceWrite, override conflict situation and force the update /// </summary> private async Task <(SyncContext, bool)> InternalApplyConflictUpdateAsync(IScopeInfo scopeInfo, SyncContext context, DbSyncAdapter syncAdapter, SyncRow row, long?lastTimestamp, Guid?senderScopeId, bool forceWrite, DbConnection connection, DbTransaction transaction) { if (row.SchemaTable == null) { throw new ArgumentException("Schema table is not present in the row"); } var(command, _) = await syncAdapter.GetCommandAsync(DbCommandType.UpdateRow, connection, transaction); if (command == null) { return(context, false); } // Set the parameters value from row syncAdapter.SetColumnParametersValues(command, row); // Set the special parameters for update syncAdapter.AddScopeParametersValues(command, senderScopeId, lastTimestamp, false, forceWrite); await this.InterceptAsync(new DbCommandArgs(context, command, connection, transaction)).ConfigureAwait(false); var rowUpdatedCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); // Check if we have a return value instead var syncRowCountParam = DbSyncAdapter.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { rowUpdatedCount = (int)syncRowCountParam.Value; } command.Dispose(); return(context, rowUpdatedCount > 0); }
/// <summary> /// Update a metadata row /// </summary> internal async Task <(SyncContext context, bool metadataUpdated)> InternalUpdateMetadatasAsync(IScopeInfo scopeInfo, SyncContext context, DbSyncAdapter syncAdapter, SyncRow row, Guid?senderScopeId, bool forceWrite, DbConnection connection, DbTransaction transaction) { context.SyncStage = SyncStage.ChangesApplying; var(command, _) = await syncAdapter.GetCommandAsync(DbCommandType.UpdateMetadata, connection, transaction); if (command == null) { return(context, false); } // Set the parameters value from row syncAdapter.SetColumnParametersValues(command, row); // Set the special parameters for update syncAdapter.AddScopeParametersValues(command, senderScopeId, 0, row.RowState == DataRowState.Deleted, forceWrite); await this.InterceptAsync(new DbCommandArgs(context, command, connection, transaction)).ConfigureAwait(false); var metadataUpdatedRowsCount = await command.ExecuteNonQueryAsync().ConfigureAwait(false); // Check if we have a return value instead var syncRowCountParam = DbSyncAdapter.GetParameter(command, "sync_row_count"); if (syncRowCountParam != null) { metadataUpdatedRowsCount = (int)syncRowCountParam.Value; } command.Dispose(); return(context, metadataUpdatedRowsCount > 0); }