Ejemplo n.º 1
0
        /// <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);
                    }
                }
            }
        }
Ejemplo n.º 4
0
 public TableChangesAppliedEventArgs(string providerTypeName, SyncStage stage, TableChangesApplied tableChangesApplied, DbConnection connection, DbTransaction transaction) : base(providerTypeName, stage, connection, transaction)
 {
     this.TableChangesApplied = tableChangesApplied;
 }
Ejemplo n.º 5
0
 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;
 }
Ejemplo n.º 7
0
        /// <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);
        }
Ejemplo n.º 8
0
        /// <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);
        }