/// <summary> /// Apply changes internal method for one type of query: Insert, Update or Delete for every batch from a table /// </summary> private async Task InternalApplyTableChangesAsync(SyncContext context, SyncTable schemaTable, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DataRowState applyType, DatabaseChangesApplied changesApplied, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { // Only table schema is replicated, no datas are applied if (schemaTable.SyncDirection == SyncDirection.None) { return; } // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && schemaTable.SyncDirection == SyncDirection.DownloadOnly) { return; } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && schemaTable.SyncDirection == SyncDirection.UploadOnly) { return; } var hasChanges = message.Changes.HasData(schemaTable.TableName, schemaTable.SchemaName); // Each table in the messages contains scope columns. Don't forget it if (hasChanges) { // launch interceptor if any var args = new TableChangesApplyingArgs(context, schemaTable, applyType, connection, transaction); await this.InterceptAsync(args, cancellationToken).ConfigureAwait(false); if (args.Cancel) { return; } TableChangesApplied tableChangesApplied = null; var enumerableOfTables = message.Changes.GetTableAsync(schemaTable.TableName, schemaTable.SchemaName, this); var enumeratorOfTable = enumerableOfTables.GetAsyncEnumerator(); // getting the table to be applied // we may have multiple batch files, so we can have multipe sync tables with the same name // We can say that dmTable may be contained in several files while (await enumeratorOfTable.MoveNextAsync()) { var syncTable = enumeratorOfTable.Current; if (syncTable == null || syncTable.Rows == null || syncTable.Rows.Count == 0) { continue; } // Creating a filtered view of my rows with the correct applyType var filteredRows = syncTable.Rows.Where(r => r.RowState == applyType); // no filtered rows, go next container table if (filteredRows.Count() == 0) { continue; } // Create an empty Set that wil contains filtered rows to apply // Need Schema for culture & case sensitive properties var changesSet = syncTable.Schema.Clone(false); var schemaChangesTable = syncTable.Clone(); changesSet.Tables.Add(schemaChangesTable); schemaChangesTable.Rows.AddRange(filteredRows.ToList()); // Should we use bulk operations ? var usBulk = message.UseBulkOperations && this.Provider.SupportBulkOperations; // Apply the changes batch var(rowsApplied, conflictsResolvedCount) = await this.InternalApplyChangesBatchAsync(context, usBulk, schemaChangesTable, message, applyType, connection, transaction, cancellationToken).ConfigureAwait(false); // Any failure ? var changedFailed = filteredRows.Count() - conflictsResolvedCount - rowsApplied; // We may have multiple batch files, so we can have multipe sync tables with the same name // We can say that a syncTable may be contained in several files // That's why we should get an applied changes instance if already exists from a previous batch file tableChangesApplied = changesApplied.TableChangesApplied.FirstOrDefault(tca => { var sc = SyncGlobalization.DataSourceStringComparison; var sn = tca.SchemaName == null ? string.Empty : tca.SchemaName; var otherSn = schemaTable.SchemaName == null ? string.Empty : schemaTable.SchemaName; return(tca.TableName.Equals(schemaTable.TableName, sc) && sn.Equals(otherSn, sc) && tca.State == applyType); }); if (tableChangesApplied == null) { tableChangesApplied = new TableChangesApplied { TableName = schemaTable.TableName, SchemaName = schemaTable.SchemaName, Applied = rowsApplied, ResolvedConflicts = conflictsResolvedCount, Failed = changedFailed, State = applyType, TotalRowsCount = message.Changes.RowsCount, TotalAppliedCount = changesApplied.TotalAppliedChanges + rowsApplied }; changesApplied.TableChangesApplied.Add(tableChangesApplied); } else { tableChangesApplied.Applied += rowsApplied; tableChangesApplied.TotalAppliedCount = changesApplied.TotalAppliedChanges; tableChangesApplied.ResolvedConflicts += conflictsResolvedCount; tableChangesApplied.Failed += changedFailed; } // we've got 0.25% to fill here var progresspct = rowsApplied * 0.25d / tableChangesApplied.TotalRowsCount; context.ProgressPercentage += progresspct; var tableChangesBatchAppliedArgs = new TableChangesBatchAppliedArgs(context, tableChangesApplied, connection, transaction); // Report the batch changes applied // We don't report progress if we do not have applied any changes on the table, to limit verbosity of Progress if (tableChangesBatchAppliedArgs.TableChangesApplied.Applied > 0 || tableChangesBatchAppliedArgs.TableChangesApplied.Failed > 0 || tableChangesBatchAppliedArgs.TableChangesApplied.ResolvedConflicts > 0) { await this.InterceptAsync(tableChangesBatchAppliedArgs, cancellationToken).ConfigureAwait(false); this.ReportProgress(context, progress, tableChangesBatchAppliedArgs, connection, transaction); } } // Report the overall changes applied for the current table if (tableChangesApplied != null) { var tableChangesAppliedArgs = new TableChangesAppliedArgs(context, tableChangesApplied, connection, transaction); // We don't report progress if we do not have applied any changes on the table, to limit verbosity of Progress if (tableChangesAppliedArgs.TableChangesApplied.Applied > 0 || tableChangesAppliedArgs.TableChangesApplied.Failed > 0 || tableChangesAppliedArgs.TableChangesApplied.ResolvedConflicts > 0) { await this.InterceptAsync(tableChangesAppliedArgs, cancellationToken).ConfigureAwait(false); } } } }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal ChangeApplicationAction ApplyChangesInternal(SyncContext context, DbConnection connection, DbTransaction transaction, ScopeInfo fromScope, BatchInfo changes, DmRowState applyType, ChangesApplied changesApplied) { ChangeApplicationAction changeApplicationAction = ChangeApplicationAction.Continue; var configuration = GetCacheConfiguration(); // for each adapters (Zero to End for Insert / Updates -- End to Zero for Deletes for (int i = 0; i < configuration.Count; i++) { // If we have a delete we must go from Up to Down, orthewise Dow to Up index var tableDescription = (applyType != DmRowState.Deleted ? configuration[i] : configuration[configuration.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 = configuration.GetApplyAction(); // Set syncAdapter properties syncAdapter.applyType = applyType; // Get conflict handler resolver if (syncAdapter.ConflictActionInvoker == null && this.ApplyChangedFailed != null) { syncAdapter.ConflictActionInvoker = GetConflictAction; } if (changes.BatchPartsInfo != null && 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 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 (configuration.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = syncAdapter.ApplyBulkChanges(dmChangesView, fromScope, conflicts); } else { rowsApplied = syncAdapter.ApplyChanges(dmChangesView, 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(connection, transaction); var localTimeStamp = scopeInfoBuilder.GetLocalTimestamp(); changeApplicationAction = syncAdapter.HandleConflict(conflict, fromScope, localTimeStamp, 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); }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal async Task ApplyChangesInternalAsync( SyncTable schemaTable, SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DataRowState applyType, DatabaseChangesApplied changesApplied, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { this.Orchestrator.logger.LogDebug(SyncEventsId.ApplyChanges, message); // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && schemaTable.SyncDirection == SyncDirection.DownloadOnly) { return; } // if we are in download stage, so check if table is not download only if (context.SyncWay == SyncWay.Download && schemaTable.SyncDirection == SyncDirection.UploadOnly) { return; } var builder = this.GetTableBuilder(schemaTable, message.Setup); var syncAdapter = builder.CreateSyncAdapter(); syncAdapter.ApplyType = applyType; var hasChanges = await message.Changes.HasDataAsync(this.Orchestrator); // Each table in the messages contains scope columns. Don't forget it if (hasChanges) { // getting the table to be applied // we may have multiple batch files, so we can have multipe sync tables with the same name // We can say that dmTable may be contained in several files foreach (var syncTable in message.Changes.GetTable(schemaTable.TableName, schemaTable.SchemaName, this.Orchestrator)) { if (syncTable == null || syncTable.Rows == null || syncTable.Rows.Count == 0) { continue; } // Creating a filtered view of my rows with the correct applyType var filteredRows = syncTable.Rows.Where(r => r.RowState == applyType); // no filtered rows, go next container table if (filteredRows.Count() == 0) { continue; } // Conflicts occured when trying to apply rows var conflicts = new List <SyncConflict>(); // Create an empty Set that wil contains filtered rows to apply // Need Schema for culture & case sensitive properties var changesSet = syncTable.Schema.Clone(false); var schemaChangesTable = syncTable.Clone(); changesSet.Tables.Add(schemaChangesTable); schemaChangesTable.Rows.AddRange(filteredRows.ToList()); if (this.Orchestrator.logger.IsEnabled(LogLevel.Trace)) { foreach (var row in schemaChangesTable.Rows) { this.Orchestrator.logger.LogTrace(SyncEventsId.ApplyChanges, row); } } // Launch any interceptor if available await this.Orchestrator.InterceptAsync(new TableChangesApplyingArgs(context, schemaChangesTable, applyType, connection, transaction), cancellationToken).ConfigureAwait(false); int rowsApplied = 0; if (message.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = await syncAdapter.ApplyBulkChangesAsync(message.LocalScopeId, message.SenderScopeId, schemaChangesTable, message.LastTimestamp, conflicts, connection, transaction); } else { rowsApplied = await syncAdapter.ApplyChangesAsync(message.LocalScopeId, message.SenderScopeId, schemaChangesTable, message.LastTimestamp, conflicts, connection, transaction); } // resolving conflicts var(rowsAppliedCount, conflictsResolvedCount, syncErrorsCount) = await ResolveConflictsAsync(context, message.LocalScopeId, message.SenderScopeId, syncAdapter, conflicts, message, connection, transaction).ConfigureAwait(false); // Add conflict rows applied that are correctly resolved, as applied rowsApplied += rowsAppliedCount; // Handle sync progress for this syncadapter (so this table) var changedFailed = filteredRows.Count() - conflictsResolvedCount - rowsApplied; // We may have multiple batch files, so we can have multipe sync tables with the same name // We can say that a syncTable may be contained in several files // That's why we should get an applied changes instance if already exists from a previous batch file var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault(tca => { var sc = SyncGlobalization.DataSourceStringComparison; var sn = tca.SchemaName == null ? string.Empty : tca.SchemaName; var otherSn = schemaTable.SchemaName == null ? string.Empty : schemaTable.SchemaName; return(tca.TableName.Equals(schemaTable.TableName, sc) && sn.Equals(otherSn, sc) && tca.State == applyType); }); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { TableName = schemaTable.TableName, SchemaName = schemaTable.SchemaName, Applied = rowsApplied, ResolvedConflicts = conflictsResolvedCount, Failed = changedFailed, State = applyType }; changesApplied.TableChangesApplied.Add(existAppliedChanges); } else { existAppliedChanges.Applied += rowsApplied; existAppliedChanges.ResolvedConflicts += conflictsResolvedCount; existAppliedChanges.Failed += changedFailed; } var tableChangesAppliedArgs = new TableChangesAppliedArgs(context, existAppliedChanges, connection, transaction); // We don't report progress if we do not have applied any changes on the table, to limit verbosity of Progress if (tableChangesAppliedArgs.TableChangesApplied.Applied > 0 || tableChangesAppliedArgs.TableChangesApplied.Failed > 0 || tableChangesAppliedArgs.TableChangesApplied.ResolvedConflicts > 0) { await this.Orchestrator.InterceptAsync(tableChangesAppliedArgs, cancellationToken).ConfigureAwait(false); this.Orchestrator.ReportProgress(context, progress, tableChangesAppliedArgs, connection, transaction); this.Orchestrator.logger.LogDebug(SyncEventsId.ApplyChanges, tableChangesAppliedArgs); } } } }
public TableChangesAppliedEventArgs(string providerTypeName, SyncStage stage, TableChangesApplied tableChangesApplied, DbConnection connection, DbTransaction transaction) : base(providerTypeName, stage, connection, transaction) { this.TableChangesApplied = tableChangesApplied; }
public TableChangesAppliedArgs(SyncContext context, TableChangesApplied tableChangesApplied, DbConnection connection, DbTransaction transaction) : base(context, connection, transaction) { this.TableChangesApplied = tableChangesApplied; }
public TableChangesAppliedEventArgs(string providerTypeName, SyncStage stage, TableChangesApplied tableChangesApplied) : base(providerTypeName, stage) { this.TableChangesApplied = tableChangesApplied; }
/// <summary> /// Apply changes internal method for one Insert or Update or Delete for every dbSyncAdapter /// </summary> internal async Task <ChangeApplicationAction> ApplyChangesInternalAsync( SyncTable schemaTable, SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction, DataRowState applyType, DatabaseChangesApplied changesApplied, CancellationToken cancellationToken, IProgress <ProgressArgs> progress = null) { // if we are in upload stage, so check if table is not download only if (context.SyncWay == SyncWay.Upload && schemaTable.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 && schemaTable.SyncDirection == SyncDirection.UploadOnly) { return(ChangeApplicationAction.Continue); } var builder = this.GetTableBuilder(schemaTable); var syncAdapter = builder.CreateSyncAdapter(connection, transaction); syncAdapter.ApplyType = applyType; var hasChanges = await message.Changes.HasDataAsync(); // Each table in the messages contains scope columns. Don't forget it if (hasChanges) { // getting the table to be applied // we may have multiple batch files, so we can have multipe sync tables with the same name // We can say that dmTable may be contained in several files foreach (var syncTable in message.Changes.GetTable(schemaTable.TableName, schemaTable.SchemaName)) { if (syncTable == null || syncTable.Rows == null || syncTable.Rows.Count == 0) { continue; } // Creating a filtered view of my rows with the correct applyType var filteredRows = syncTable.Rows.Where(r => r.RowState == applyType); // no filtered rows, go next container table if (filteredRows.Count() == 0) { 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, filteredRows, schemaTable, applyType, connection, transaction)).ConfigureAwait(false); // Create an empty Set that wil contains filtered rows to apply // Need Schema for culture & case sensitive properties var changesSet = syncTable.Schema.Clone(false); var schemaChangesTable = syncTable.Clone(); changesSet.Tables.Add(schemaChangesTable); schemaChangesTable.Rows.AddRange(filteredRows.ToList()); int rowsApplied = 0; if (message.UseBulkOperations && this.SupportBulkOperations) { rowsApplied = syncAdapter.ApplyBulkChanges(message.LocalScopeId, message.SenderScopeId, schemaChangesTable, message.LastTimestamp, conflicts); } else { rowsApplied = syncAdapter.ApplyChanges(message.LocalScopeId, message.SenderScopeId, schemaChangesTable, message.LastTimestamp, conflicts); } // resolving conflicts (var changeApplicationAction, var conflictRowsApplied) = await ResolveConflictsAsync(context, message.LocalScopeId, message.SenderScopeId, syncAdapter, conflicts, message, connection, transaction).ConfigureAwait(false); if (changeApplicationAction == ChangeApplicationAction.Rollback) { return(ChangeApplicationAction.Rollback); } // Add conflict rows that are correctly resolved, as applied rowsApplied += conflictRowsApplied; // Handle sync progress for this syncadapter (so this table) var changedFailed = filteredRows.Count() - rowsApplied; // raise SyncProgress Event var existAppliedChanges = changesApplied.TableChangesApplied.FirstOrDefault( sc => string.Equals(sc.Table.TableName, schemaTable.TableName, SyncGlobalization.DataSourceStringComparison) && sc.State == applyType); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { Table = schemaTable, 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, progress, tableChangesAppliedArgs, connection, transaction); await this.InterceptAsync(tableChangesAppliedArgs).ConfigureAwait(false); } } return(ChangeApplicationAction.Continue); }
/// <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.TableName, 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.TableName, table.TableName) && sc.State == applyType); if (existAppliedChanges == null) { existAppliedChanges = new TableChangesApplied { TableName = table.TableName, 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); }