Example #1
0
        /// <summary>
        /// Apply changes : Delete / Insert / Update
        /// the fromScope is local client scope when this method is called from server
        /// the fromScope is server scope when this method is called from client
        /// </summary>
        public virtual async Task <(SyncContext, DatabaseChangesApplied)> ApplyChangesAsync(SyncContext context, MessageApplyChanges message)
        {
            var           changeApplicationAction = ChangeApplicationAction.Continue;
            DbTransaction applyTransaction        = null;
            DbConnection  connection     = null;
            var           changesApplied = new DatabaseChangesApplied();

            try
            {
                using (connection = this.CreateConnection())
                {
                    await connection.OpenAsync();

                    // Create a transaction
                    applyTransaction = connection.BeginTransaction();

                    context.SyncStage = SyncStage.DatabaseChangesApplying;
                    // Launch any interceptor if available
                    await this.InterceptAsync(new DatabaseChangesApplyingArgs(context, connection, applyTransaction));

                    // Disable check constraints
                    if (this.Options.DisableConstraintsOnApplyChanges)
                    {
                        changeApplicationAction = this.DisableConstraints(context, message.Schema, connection, applyTransaction, message.FromScope);
                    }

                    // -----------------------------------------------------
                    // 0) Check if we are in a reinit mode
                    // -----------------------------------------------------
                    if (context.SyncWay == SyncWay.Download && context.SyncType != SyncType.Normal)
                    {
                        changeApplicationAction = this.ResetInternal(context, message.Schema, connection, applyTransaction, message.FromScope);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw new SyncException("Rollback during reset tables", context.SyncStage, SyncExceptionType.Rollback);
                        }
                    }

                    // -----------------------------------------------------
                    // 1) Applying deletes. Do not apply deletes if we are in a new database
                    // -----------------------------------------------------
                    if (!message.FromScope.IsNewScope)
                    {
                        // for delete we must go from Up to Down
                        foreach (var table in message.Schema.Tables.Reverse())
                        {
                            changeApplicationAction = await this.ApplyChangesInternalAsync(table, context, message, connection,
                                                                                           applyTransaction, DmRowState.Deleted, changesApplied);
                        }

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            RaiseRollbackException(context, "Rollback during applying deletes");
                        }
                    }

                    // -----------------------------------------------------
                    // 2) Applying Inserts and Updates. Apply in table order
                    // -----------------------------------------------------
                    foreach (var table in message.Schema.Tables)
                    {
                        changeApplicationAction = await this.ApplyChangesInternalAsync(table, context, message, connection,
                                                                                       applyTransaction, DmRowState.Added, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            RaiseRollbackException(context, "Rollback during applying inserts");
                        }

                        changeApplicationAction = await this.ApplyChangesInternalAsync(table, context, message, connection,
                                                                                       applyTransaction, DmRowState.Modified, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            RaiseRollbackException(context, "Rollback during applying updates");
                        }
                    }


                    // Progress & Interceptor
                    context.SyncStage = SyncStage.DatabaseChangesApplied;
                    var databaseChangesAppliedArgs = new DatabaseChangesAppliedArgs(context, connection, applyTransaction);
                    this.ReportProgress(context, databaseChangesAppliedArgs, connection, applyTransaction);
                    await this.InterceptAsync(databaseChangesAppliedArgs);

                    // Re enable check constraints
                    if (this.Options.DisableConstraintsOnApplyChanges)
                    {
                        changeApplicationAction = this.EnableConstraints(context, message.Schema, connection, applyTransaction, message.FromScope);
                    }

                    applyTransaction.Commit();


                    return(context, changesApplied);
                }
            }
            catch (SyncException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new SyncException(ex, SyncStage.TableChangesApplying);
            }
            finally
            {
                if (applyTransaction != null)
                {
                    applyTransaction.Dispose();
                    applyTransaction = null;
                }

                if (connection != null && connection.State == ConnectionState.Open)
                {
                    connection.Close();
                }

                if (message.Changes != null)
                {
                    message.Changes.Clear(this.Options.CleanMetadatas);
                }
            }
        }
        InternalApplyChangesAsync(SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction,
                                  CancellationToken cancellationToken, IProgress <ProgressArgs> progress)
        {
            // call interceptor
            await this.InterceptAsync(new DatabaseChangesApplyingArgs(context, message, connection, transaction), cancellationToken).ConfigureAwait(false);

            var changesApplied = new DatabaseChangesApplied();

            // Check if we have some data available
            var hasChanges = message.Changes.HasData();

            // if we have changes or if we are in re init mode
            if (hasChanges || context.SyncType != SyncType.Normal)
            {
                // Disable check constraints
                // Because Sqlite does not support "PRAGMA foreign_keys=OFF" Inside a transaction
                // Report this disabling constraints brefore opening a transaction
                if (message.DisableConstraintsOnApplyChanges)
                {
                    foreach (var table in message.Schema.Tables.Reverse())
                    {
                        await this.InternalDisableConstraintsAsync(context, this.GetSyncAdapter(table, message.Setup), connection, transaction).ConfigureAwait(false);
                    }
                }

                // -----------------------------------------------------
                // 0) Check if we are in a reinit mode (Check also SyncWay to be sure we don't reset tables on server)
                // -----------------------------------------------------
                if (context.SyncWay == SyncWay.Download && context.SyncType != SyncType.Normal)
                {
                    foreach (var table in message.Schema.Tables)
                    {
                        await this.InternalResetTableAsync(context, this.GetSyncAdapter(table, message.Setup), connection, transaction).ConfigureAwait(false);
                    }
                }

                // -----------------------------------------------------
                // 1) Applying deletes. Do not apply deletes if we are in a new database
                // -----------------------------------------------------
                if (!message.IsNew && hasChanges)
                {
                    foreach (var table in message.Schema.Tables.Reverse())
                    {
                        await this.InternalApplyTableChangesAsync(context, table, message, connection,
                                                                  transaction, DataRowState.Deleted, changesApplied, cancellationToken, progress).ConfigureAwait(false);
                    }
                }

                // -----------------------------------------------------
                // 2) Applying Inserts and Updates. Apply in table order
                // -----------------------------------------------------
                if (hasChanges)
                {
                    foreach (var table in message.Schema.Tables)
                    {
                        await this.InternalApplyTableChangesAsync(context, table, message, connection,
                                                                  transaction, DataRowState.Modified, changesApplied, cancellationToken, progress).ConfigureAwait(false);
                    }
                }

                // Re enable check constraints
                if (message.DisableConstraintsOnApplyChanges)
                {
                    foreach (var table in message.Schema.Tables)
                    {
                        await this.InternalEnableConstraintsAsync(context, this.GetSyncAdapter(table, message.Setup), connection, transaction).ConfigureAwait(false);
                    }
                }
            }


            // Before cleaning, check if we are not applying changes from a snapshotdirectory
            var cleanFolder = message.CleanFolder;

            if (cleanFolder)
            {
                cleanFolder = await this.InternalCanCleanFolderAsync(context, message.Changes, cancellationToken, progress).ConfigureAwait(false);
            }

            // clear the changes because we don't need them anymore
            message.Changes.Clear(cleanFolder);

            var databaseChangesAppliedArgs = new DatabaseChangesAppliedArgs(context, changesApplied, connection, transaction);

            await this.InterceptAsync(databaseChangesAppliedArgs, cancellationToken).ConfigureAwait(false);

            this.ReportProgress(context, progress, databaseChangesAppliedArgs);

            return(context, changesApplied);
        }
Example #3
0
        InternalApplyChangesAsync(IScopeInfo scopeInfo, SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction,
                                  CancellationToken cancellationToken, IProgress <ProgressArgs> progress)
        {
            context.SyncStage = SyncStage.ChangesApplying;
            // call interceptor
            var databaseChangesApplyingArgs = new DatabaseChangesApplyingArgs(context, message, connection, transaction);

            await this.InterceptAsync(databaseChangesApplyingArgs, progress, cancellationToken).ConfigureAwait(false);

            var changesApplied = new DatabaseChangesApplied();

            // Check if we have some data available
            var hasChanges = message.BatchInfo.HasData();

            // if we have changes or if we are in re init mode
            if (hasChanges || context.SyncType != SyncType.Normal)
            {
                var schemaTables = message.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable()));

                // Disable check constraints
                // Because Sqlite does not support "PRAGMA foreign_keys=OFF" Inside a transaction
                // Report this disabling constraints brefore opening a transaction
                if (message.DisableConstraintsOnApplyChanges)
                {
                    foreach (var table in schemaTables)
                    {
                        var syncAdapter = this.GetSyncAdapter(table, scopeInfo);
                        context = await this.InternalDisableConstraintsAsync(scopeInfo, context, syncAdapter, connection, transaction).ConfigureAwait(false);
                    }
                }

                // -----------------------------------------------------
                // 0) Check if we are in a reinit mode (Check also SyncWay to be sure we don't reset tables on server, then check if we don't have already applied a snapshot)
                // -----------------------------------------------------
                if (context.SyncWay == SyncWay.Download && context.SyncType != SyncType.Normal && !message.SnapshoteApplied)
                {
                    foreach (var table in schemaTables.Reverse())
                    {
                        var syncAdapter = this.GetSyncAdapter(table, scopeInfo);
                        context = await this.InternalResetTableAsync(scopeInfo, context, syncAdapter, connection, transaction).ConfigureAwait(false);
                    }
                }

                // Trying to change order (from deletes-upserts to upserts-deletes)
                // see https://github.com/Mimetis/Dotmim.Sync/discussions/453#discussioncomment-380530

                // -----------------------------------------------------
                // 1) Applying Inserts and Updates. Apply in table order
                // -----------------------------------------------------
                if (hasChanges)
                {
                    foreach (var table in schemaTables)
                    {
                        context = await this.InternalApplyTableChangesAsync(scopeInfo, context, table, message, connection, transaction,
                                                                            DataRowState.Modified, changesApplied, cancellationToken, progress).ConfigureAwait(false);
                    }
                }

                // -----------------------------------------------------
                // 2) Applying Deletes. Do not apply deletes if we are in a new database
                // -----------------------------------------------------
                if (!message.IsNew && hasChanges)
                {
                    foreach (var table in schemaTables.Reverse())
                    {
                        context = await this.InternalApplyTableChangesAsync(scopeInfo, context, table, message, connection, transaction,
                                                                            DataRowState.Deleted, changesApplied, cancellationToken, progress).ConfigureAwait(false);
                    }
                }

                // Re enable check constraints
                if (message.DisableConstraintsOnApplyChanges)
                {
                    foreach (var table in schemaTables)
                    {
                        context = await this.InternalEnableConstraintsAsync(scopeInfo, context, this.GetSyncAdapter(table, scopeInfo), connection, transaction).ConfigureAwait(false);
                    }
                }

                // Dispose data
                message.BatchInfo.Clear(false);
            }

            // Before cleaning, check if we are not applying changes from a snapshotdirectory
            var cleanFolder = message.CleanFolder;

            if (cleanFolder)
            {
                cleanFolder = await this.InternalCanCleanFolderAsync(scopeInfo.Name, context.Parameters, message.BatchInfo, cancellationToken, progress).ConfigureAwait(false);
            }

            // clear the changes because we don't need them anymore
            message.BatchInfo.Clear(cleanFolder);

            var databaseChangesAppliedArgs = new DatabaseChangesAppliedArgs(context, changesApplied, connection, transaction);

            await this.InterceptAsync(databaseChangesAppliedArgs, progress, cancellationToken).ConfigureAwait(false);

            return(context, changesApplied);
        }
Example #4
0
        ApplyChangesAsync(ScopeInfo scope, SyncSet schema, BatchInfo serverBatchInfo,
                          long clientTimestamp, long remoteClientTimestamp, ConflictResolutionPolicy policy,
                          CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null)
        {
            if (!this.StartTime.HasValue)
            {
                this.StartTime = DateTime.UtcNow;
            }

            // Get context or create a new one
            var ctx = this.GetContext();
            DatabaseChangesApplied clientChangesApplied = null;

            using (var connection = this.Provider.CreateConnection())
            {
                try
                {
                    ctx.SyncStage = SyncStage.ChangesApplying;


                    // Open connection
                    await this.OpenConnectionAsync(connection, cancellationToken).ConfigureAwait(false);

                    // Create a transaction
                    using (var transaction = connection.BeginTransaction())
                    {
                        await this.InterceptAsync(new TransactionOpenedArgs(ctx, connection, transaction), cancellationToken).ConfigureAwait(false);

                        // lastSyncTS : apply lines only if they are not modified since last client sync
                        var lastSyncTS = scope.LastSyncTimestamp;
                        // isNew : if IsNew, don't apply deleted rows from server
                        var isNew = scope.IsNewScope;
                        // We are in downloading mode
                        ctx.SyncWay = SyncWay.Download;


                        // Create the message containing everything needed to apply changes
                        var applyChanges = new MessageApplyChanges(scope.Id, Guid.Empty, isNew, lastSyncTS, schema, this.Setup, policy,
                                                                   this.Options.DisableConstraintsOnApplyChanges,
                                                                   this.Options.UseBulkOperations, this.Options.CleanMetadatas, this.Options.CleanFolder,
                                                                   serverBatchInfo);

                        // call interceptor
                        await this.InterceptAsync(new DatabaseChangesApplyingArgs(ctx, applyChanges, connection, transaction), cancellationToken).ConfigureAwait(false);

                        // Call apply changes on provider
                        (ctx, clientChangesApplied) = await this.Provider.ApplyChangesAsync(ctx, applyChanges, connection, transaction, cancellationToken, progress).ConfigureAwait(false);


                        if (cancellationToken.IsCancellationRequested)
                        {
                            cancellationToken.ThrowIfCancellationRequested();
                        }

                        // check if we need to delete metadatas
                        if (this.Options.CleanMetadatas && clientChangesApplied.TotalAppliedChanges > 0)
                        {
                            await this.Provider.DeleteMetadatasAsync(ctx, schema, this.Setup, lastSyncTS, connection, transaction, cancellationToken, progress);
                        }

                        // now the sync is complete, remember the time
                        this.CompleteTime = DateTime.UtcNow;

                        // generate the new scope item
                        scope.IsNewScope              = false;
                        scope.LastSync                = this.CompleteTime;
                        scope.LastSyncTimestamp       = clientTimestamp;
                        scope.LastServerSyncTimestamp = remoteClientTimestamp;
                        scope.LastSyncDuration        = this.CompleteTime.Value.Subtract(this.StartTime.Value).Ticks;
                        scope.Setup = this.Setup;

                        // Write scopes locally
                        ctx = await this.Provider.WriteClientScopeAsync(ctx, this.Options.ScopeInfoTableName, scope, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

                        await this.InterceptAsync(new TransactionCommitArgs(ctx, connection, transaction), cancellationToken).ConfigureAwait(false);

                        transaction.Commit();
                    }

                    ctx.SyncStage = SyncStage.ChangesApplied;

                    await this.CloseConnectionAsync(connection, cancellationToken).ConfigureAwait(false);

                    this.logger.LogInformation(SyncEventsId.ApplyChanges, clientChangesApplied);

                    var databaseChangesAppliedArgs = new DatabaseChangesAppliedArgs(ctx, clientChangesApplied, connection);
                    await this.InterceptAsync(databaseChangesAppliedArgs, cancellationToken).ConfigureAwait(false);

                    this.ReportProgress(ctx, progress, databaseChangesAppliedArgs);
                }
                catch (Exception ex)
                {
                    RaiseError(ex);
                }
                finally
                {
                    if (connection != null && connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }
                }
                return(clientChangesApplied, scope);
            }
        }
Example #5
0
        ApplyChangesAsync(SyncContext context, MessageApplyChanges message, DbConnection connection, DbTransaction transaction,
                          CancellationToken cancellationToken, IProgress <ProgressArgs> progress = null)
        {
            var changeApplicationAction = ChangeApplicationAction.Continue;
            var changesApplied          = new DatabaseChangesApplied();

            var hasChanges = await message.Changes.HasDataAsync();

            // Check if we have some data available
            if (!hasChanges)
            {
                return(context, changesApplied);
            }

            context.SyncStage = SyncStage.DatabaseChangesApplying;

            // Launch any interceptor if available
            await this.InterceptAsync(new DatabaseChangesApplyingArgs(context, connection, transaction)).ConfigureAwait(false);

            // Disable check constraints
            // Because Sqlite does not support "PRAGMA foreign_keys=OFF" Inside a transaction
            // Report this disabling constraints brefore opening a transaction
            if (message.DisableConstraintsOnApplyChanges)
            {
                this.DisableConstraints(context, message.Schema, connection, transaction);
            }

            // -----------------------------------------------------
            // 0) Check if we are in a reinit mode
            // -----------------------------------------------------
            if (context.SyncWay == SyncWay.Download && context.SyncType != SyncType.Normal)
            {
                changeApplicationAction = this.ResetInternal(context, message.Schema, connection, transaction);


                // Rollback
                if (changeApplicationAction == ChangeApplicationAction.Rollback)
                {
                    throw new RollbackException("Rollback during reset tables");
                }
            }

            // -----------------------------------------------------
            // 1) Applying deletes. Do not apply deletes if we are in a new database
            // -----------------------------------------------------
            if (!message.IsNew)
            {
                // for delete we must go from Up to Down
                foreach (var table in message.Schema.Tables.Reverse())
                {
                    changeApplicationAction = await this.ApplyChangesInternalAsync(table, context, message, connection,
                                                                                   transaction, DataRowState.Deleted, changesApplied, cancellationToken, progress).ConfigureAwait(false);
                }

                // Rollback
                if (changeApplicationAction == ChangeApplicationAction.Rollback)
                {
                    throw new RollbackException("Rollback during applying deletes");
                }
            }

            // -----------------------------------------------------
            // 2) Applying Inserts and Updates. Apply in table order
            // -----------------------------------------------------
            foreach (var table in message.Schema.Tables)
            {
                changeApplicationAction = await this.ApplyChangesInternalAsync(table, context, message, connection,
                                                                               transaction, DataRowState.Modified, changesApplied, cancellationToken, progress).ConfigureAwait(false);

                // Rollback
                if (changeApplicationAction == ChangeApplicationAction.Rollback)
                {
                    throw new RollbackException("Rollback during applying updates");
                }
            }


            // Progress & Interceptor
            context.SyncStage = SyncStage.DatabaseChangesApplied;
            var databaseChangesAppliedArgs = new DatabaseChangesAppliedArgs(context, changesApplied, connection, transaction);

            this.ReportProgress(context, progress, databaseChangesAppliedArgs, connection, transaction);
            await this.InterceptAsync(databaseChangesAppliedArgs).ConfigureAwait(false);

            // Re enable check constraints
            if (message.DisableConstraintsOnApplyChanges)
            {
                this.EnableConstraints(context, message.Schema, connection, transaction);
            }

            // clear the changes because we don't need them anymore
            message.Changes.Clear(message.CleanFolder);

            return(context, changesApplied);
        }