/// <summary> /// Launch apply bulk changes /// </summary> /// <returns></returns> public int ApplyBulkChanges(DmView dmChanges, ScopeInfo fromScope, List <SyncConflict> conflicts) { DbCommand bulkCommand = null; if (this.applyType == DmRowState.Added) { bulkCommand = this.GetCommand(DbObjectType.BulkInsertProcName); } else if (this.applyType == DmRowState.Modified) { bulkCommand = this.GetCommand(DbObjectType.BulkUpdateProcName); } else if (this.applyType == DmRowState.Deleted) { bulkCommand = this.GetCommand(DbObjectType.BulkDeleteProcName); } else { throw new Exception("DmRowState not valid during ApplyBulkChanges operation"); } if (Transaction != null && Transaction.Connection != null) { bulkCommand.Transaction = Transaction; } DmTable batchDmTable = dmChanges.Table.Clone(); DmTable failedDmtable = new DmTable { Culture = CultureInfo.InvariantCulture }; // Create the schema for failed rows (just add the Primary keys) this.AddSchemaForFailedRowsTable(batchDmTable, failedDmtable); int batchCount = 0; int rowCount = 0; foreach (var dmRow in dmChanges) { // Cancel the delete state to be able to get the row, more simplier if (applyType == DmRowState.Deleted) { dmRow.RejectChanges(); } // Load the datarow DmRow dataRow = batchDmTable.LoadDataRow(dmRow.ItemArray, false); // Apply the delete // is it mandatory ? if (applyType == DmRowState.Deleted) { dmRow.Delete(); } batchCount++; rowCount++; if (batchCount != 500 && rowCount != dmChanges.Count) { continue; } // Since the update and create timestamp come from remote, change name for the bulk operations batchDmTable.Columns["update_timestamp"].ColumnName = "update_timestamp"; batchDmTable.Columns["create_timestamp"].ColumnName = "create_timestamp"; // execute the batch, through the provider ExecuteBatchCommand(bulkCommand, batchDmTable, failedDmtable, fromScope); // Clear the batch batchDmTable.Clear(); // Recreate a Clone // TODO : Evaluate if it's necessary batchDmTable = dmChanges.Table.Clone(); batchCount = 0; } // Update table progress //tableProgress.ChangesApplied = dmChanges.Count - failedDmtable.Rows.Count; if (failedDmtable.Rows.Count == 0) { return(dmChanges.Count); } // Check all conflicts raised var failedFilter = new Predicate <DmRow>(row => { if (row.RowState == DmRowState.Deleted) { return(failedDmtable.FindByKey(row.GetKeyValues(DmRowVersion.Original)) != null); } else { return(failedDmtable.FindByKey(row.GetKeyValues()) != null); } }); // New View var dmFailedRows = new DmView(dmChanges, failedFilter); // Generate a conflict and add it foreach (var dmFailedRow in dmFailedRows) { conflicts.Add(GetConflict(dmFailedRow)); } int failedRows = dmFailedRows.Count; // Dispose the failed view dmFailedRows.Dispose(); // return applied rows - failed rows (generating a conflict) return(dmChanges.Count - failedRows); }
/// <summary> /// Launch apply bulk changes /// </summary> /// <returns></returns> public int ApplyBulkChanges(DmView dmChanges, ScopeInfo fromScope, List <SyncConflict> conflicts) { DbCommand bulkCommand = null; if (this.ApplyType == DmRowState.Added) { bulkCommand = this.GetCommand(DbCommandType.BulkInsertRows); this.SetCommandParameters(DbCommandType.BulkInsertRows, bulkCommand); } else if (this.ApplyType == DmRowState.Modified) { bulkCommand = this.GetCommand(DbCommandType.BulkUpdateRows); this.SetCommandParameters(DbCommandType.BulkUpdateRows, bulkCommand); } else if (this.ApplyType == DmRowState.Deleted) { bulkCommand = this.GetCommand(DbCommandType.BulkDeleteRows); this.SetCommandParameters(DbCommandType.BulkDeleteRows, bulkCommand); } else { throw new Exception("DmRowState not valid during ApplyBulkChanges operation"); } if (Transaction != null && Transaction.Connection != null) { bulkCommand.Transaction = Transaction; } //DmTable batchDmTable = dmChanges.Table.Clone(); DmTable failedDmtable = new DmTable { Culture = CultureInfo.InvariantCulture }; // Create the schema for failed rows (just add the Primary keys) this.AddSchemaForFailedRowsTable(failedDmtable); // Since the update and create timestamp come from remote, change name for the bulk operations var update_timestamp_column = dmChanges.Table.Columns["update_timestamp"].ColumnName; dmChanges.Table.Columns["update_timestamp"].ColumnName = "update_timestamp"; var create_timestamp_column = dmChanges.Table.Columns["create_timestamp"].ColumnName; dmChanges.Table.Columns["create_timestamp"].ColumnName = "create_timestamp"; // Make some parts of BATCH_SIZE for (int step = 0; step < dmChanges.Count; step += BATCH_SIZE) { // get upper bound max value var taken = step + BATCH_SIZE >= dmChanges.Count ? dmChanges.Count - step : BATCH_SIZE; using (var dmStepChanges = dmChanges.Take(step, taken)) { // execute the batch, through the provider ExecuteBatchCommand(bulkCommand, dmStepChanges, failedDmtable, fromScope); } } // Disposing command if (bulkCommand != null) { bulkCommand.Dispose(); bulkCommand = null; } // Since the update and create timestamp come from remote, change name for the bulk operations dmChanges.Table.Columns["update_timestamp"].ColumnName = update_timestamp_column; dmChanges.Table.Columns["create_timestamp"].ColumnName = create_timestamp_column; //foreach (var dmRow in dmChanges) //{ // // Cancel the delete state to be able to get the row, more simplier // if (applyType == DmRowState.Deleted) // dmRow.RejectChanges(); // // Load the datarow // DmRow dataRow = batchDmTable.LoadDataRow(dmRow.ItemArray, false); // // Apply the delete // // is it mandatory ? // if (applyType == DmRowState.Deleted) // dmRow.Delete(); // batchCount++; // rowCount++; // if (batchCount < BATCH_SIZE && rowCount < dmChanges.Count) // continue; // // Since the update and create timestamp come from remote, change name for the bulk operations // batchDmTable.Columns["update_timestamp"].ColumnName = "update_timestamp"; // batchDmTable.Columns["create_timestamp"].ColumnName = "create_timestamp"; // // execute the batch, through the provider // ExecuteBatchCommand(bulkCommand, batchDmTable, failedDmtable, fromScope); // // Clear the batch // batchDmTable.Clear(); // // Recreate a Clone // // TODO : Evaluate if it's necessary // batchDmTable = dmChanges.Table.Clone(); // batchCount = 0; //} // Update table progress //tableProgress.ChangesApplied = dmChanges.Count - failedDmtable.Rows.Count; if (failedDmtable.Rows.Count == 0) { return(dmChanges.Count); } // Check all conflicts raised var failedFilter = new Predicate <DmRow>(row => { if (row.RowState == DmRowState.Deleted) { return(failedDmtable.FindByKey(row.GetKeyValues(DmRowVersion.Original)) != null); } else { return(failedDmtable.FindByKey(row.GetKeyValues()) != null); } }); // New View var dmFailedRows = new DmView(dmChanges, failedFilter); // Generate a conflict and add it foreach (var dmFailedRow in dmFailedRows) { conflicts.Add(GetConflict(dmFailedRow)); } int failedRows = dmFailedRows.Count; // Dispose the failed view dmFailedRows.Dispose(); dmFailedRows = null; // return applied rows - failed rows (generating a conflict) return(dmChanges.Count - failedRows); }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal async Task <ChangeApplicationAction> ApplyChangesInternalAsync( DmTable table, SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DmRowState applyType, DatabaseChangesApplied changesApplied) { var changeApplicationAction = ChangeApplicationAction.Continue; // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && table.SyncDirection == SyncDirection.DownloadOnly) { return(ChangeApplicationAction.Continue); } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && table.SyncDirection == SyncDirection.UploadOnly) { return(ChangeApplicationAction.Continue); } var builder = this.GetDatabaseBuilder(table); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ApplyType = applyType; if (message.Changes.BatchPartsInfo != null && message.Changes.BatchPartsInfo.Count > 0) { // getting the table to be applied // we may have multiple batch files, so we can have multipe dmTable with the same Name // We can say that dmTable may be contained in several files foreach (var dmTablePart in message.Changes.GetTable(table.TableName)) { if (dmTablePart == null || dmTablePart.Rows.Count == 0) { continue; } // check and filter var dmChangesView = new DmView(dmTablePart, (r) => r.RowState == applyType); if (dmChangesView.Count == 0) { dmChangesView.Dispose(); dmChangesView = null; continue; } // Conflicts occured when trying to apply rows var conflicts = new List <SyncConflict>(); context.SyncStage = SyncStage.TableChangesApplying; // Launch any interceptor if available await this.InterceptAsync(new TableChangesApplyingArgs(context, table, applyType, connection, transaction)); int rowsApplied; // applying the bulkchanges command if (this.Options.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = syncAdapter.ApplyBulkChanges(dmChangesView, message.FromScope, conflicts); } else { rowsApplied = syncAdapter.ApplyChanges(dmChangesView, message.FromScope, conflicts); } // If conflicts occured // Eventuall, conflicts are resolved on server side. if (conflicts != null && conflicts.Count > 0) { foreach (var conflict in conflicts) { //var scopeBuilder = this.GetScopeBuilder(); //var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(message.ScopeInfoTableName, connection, transaction); //var localTimeStamp = scopeInfoBuilder.GetLocalTimestamp(); var fromScopeLocalTimeStamp = message.FromScope.Timestamp; var conflictCount = 0; DmRow resolvedRow = null; (changeApplicationAction, conflictCount, resolvedRow) = await this.HandleConflictAsync(syncAdapter, context, conflict, message.Policy, message.FromScope, fromScopeLocalTimeStamp, connection, transaction); if (changeApplicationAction == ChangeApplicationAction.Continue) { // row resolved if (resolvedRow != null) { context.TotalSyncConflicts += conflictCount; rowsApplied++; } } else { context.TotalSyncErrors++; // TODO : Should we break at the first error ? return(ChangeApplicationAction.Rollback); } } } // Handle sync progress for this syncadapter (so this table) var changedFailed = dmChangesView.Count - rowsApplied; // raise SyncProgress Event var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault( sc => string.Equals(sc.Table.TableName, table.TableName) && sc.State == applyType); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { Table = new DmTableSurrogate(table), Applied = rowsApplied, Failed = changedFailed, State = applyType }; changesApplied.TableChangesApplied.Add(existAppliedChanges); } else { existAppliedChanges.Applied += rowsApplied; existAppliedChanges.Failed += changedFailed; } // Progress & Interceptor context.SyncStage = SyncStage.TableChangesApplied; var tableChangesAppliedArgs = new TableChangesAppliedArgs(context, existAppliedChanges, connection, transaction); this.ReportProgress(context, tableChangesAppliedArgs, connection, transaction); await this.InterceptAsync(tableChangesAppliedArgs); } } return(ChangeApplicationAction.Continue); }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal ChangeApplicationAction ApplyChangesInternal( SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DmRowState applyType, ChangesApplied changesApplied) { ChangeApplicationAction changeApplicationAction = ChangeApplicationAction.Continue; // for each adapters (Zero to End for Insert / Updates -- End to Zero for Deletes for (int i = 0; i < message.Schema.Tables.Count; i++) { // If we have a delete we must go from Up to Down, orthewise Dow to Up index var tableDescription = (applyType != DmRowState.Deleted ? message.Schema.Tables[i] : message.Schema.Tables[message.Schema.Tables.Count - i - 1]); // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && tableDescription.SyncDirection == SyncDirection.DownloadOnly) { continue; } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && tableDescription.SyncDirection == SyncDirection.UploadOnly) { continue; } var builder = this.GetDatabaseBuilder(tableDescription); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ConflictApplyAction = SyncConfiguration.GetApplyAction(message.Policy); // Set syncAdapter properties syncAdapter.applyType = applyType; // Get conflict handler resolver if (syncAdapter.ConflictActionInvoker == null && this.ApplyChangedFailed != null) { syncAdapter.ConflictActionInvoker = GetConflictAction; } if (message.Changes.BatchPartsInfo != null && message.Changes.BatchPartsInfo.Count > 0) { // getting the table to be applied // we may have multiple batch files, so we can have multipe dmTable with the same Name // We can say that dmTable may be contained in several files foreach (DmTable dmTablePart in message.Changes.GetTable(tableDescription.TableName)) { if (dmTablePart == null || dmTablePart.Rows.Count == 0) { continue; } // check and filter var dmChangesView = new DmView(dmTablePart, (r) => r.RowState == applyType); if (dmChangesView.Count == 0) { dmChangesView.Dispose(); dmChangesView = null; continue; } // Conflicts occured when trying to apply rows List <SyncConflict> conflicts = new List <SyncConflict>(); // Raise event progress only if there are rows to be applied context.SyncStage = SyncStage.TableChangesApplying; var args = new TableChangesApplyingEventArgs(this.ProviderTypeName, context.SyncStage, tableDescription.TableName, applyType); this.TryRaiseProgressEvent(args, this.TableChangesApplying); int rowsApplied; // applying the bulkchanges command if (message.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = syncAdapter.ApplyBulkChanges(dmChangesView, message.FromScope, conflicts); } else { rowsApplied = syncAdapter.ApplyChanges(dmChangesView, message.FromScope, conflicts); } // If conflicts occured // Eventuall, conflicts are resolved on server side. if (conflicts != null && conflicts.Count > 0) { foreach (var conflict in conflicts) { //var scopeBuilder = this.GetScopeBuilder(); //var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(message.ScopeInfoTableName, connection, transaction); //var localTimeStamp = scopeInfoBuilder.GetLocalTimestamp(); var fromScopeLocalTimeStamp = message.FromScope.Timestamp; changeApplicationAction = syncAdapter.HandleConflict(conflict, message.Policy, message.FromScope, fromScopeLocalTimeStamp, out DmRow resolvedRow); if (changeApplicationAction == ChangeApplicationAction.Continue) { // row resolved if (resolvedRow != null) { rowsApplied++; } } else { context.TotalSyncErrors++; // TODO : Should we break at the first error ? return(ChangeApplicationAction.Rollback); } } } // Get all conflicts resolved context.TotalSyncConflicts = conflicts.Where(c => c.Type != ConflictType.ErrorsOccurred).Sum(c => 1); // Handle sync progress for this syncadapter (so this table) var changedFailed = dmChangesView.Count - rowsApplied; // raise SyncProgress Event var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault( sc => string.Equals(sc.TableName, tableDescription.TableName) && sc.State == applyType); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { TableName = tableDescription.TableName, Applied = rowsApplied, Failed = changedFailed, State = applyType }; changesApplied.TableChangesApplied.Add(existAppliedChanges); } else { existAppliedChanges.Applied += rowsApplied; existAppliedChanges.Failed += changedFailed; } // Event progress context.SyncStage = SyncStage.TableChangesApplied; var progressEventArgs = new TableChangesAppliedEventArgs(this.ProviderTypeName, context.SyncStage, existAppliedChanges); this.TryRaiseProgressEvent(progressEventArgs, this.TableChangesApplied); } } // Dispose conflict handler resolver if (syncAdapter.ConflictActionInvoker != null) { syncAdapter.ConflictActionInvoker = null; } } return(ChangeApplicationAction.Continue); }