Пример #1
0
        /// <summary>
        /// Gets a batch of changes to synchronize when given batch size,
        /// destination knowledge, and change data retriever parameters.
        /// </summary>
        /// <returns>A DbSyncContext object that will be used to retrieve the modified data.</returns>
        internal virtual async Task <(SyncContext, BatchInfo, DatabaseChangesSelected)> InternalGetChangesAsync(
            IScopeInfo scopeInfo, SyncContext context, bool isNew, long?fromLastTimestamp, long?toNewTimestamp, Guid?excludingScopeId,
            bool supportsMultiActiveResultSets, string batchRootDirectory, string batchDirectoryName,
            DbConnection connection, DbTransaction transaction,
            CancellationToken cancellationToken, IProgress <ProgressArgs> progress)
        {
            // batch info containing changes
            BatchInfo batchInfo;

            // Statistics about changes that are selected
            DatabaseChangesSelected changesSelected;

            context.SyncStage = SyncStage.ChangesSelecting;

            if (context.SyncWay == SyncWay.Upload && context.SyncType == SyncType.Reinitialize)
            {
                (batchInfo, changesSelected) = await this.InternalGetEmptyChangesAsync(scopeInfo, batchRootDirectory).ConfigureAwait(false);

                return(context, batchInfo, changesSelected);
            }

            // create local directory
            if (!string.IsNullOrEmpty(batchRootDirectory) && !Directory.Exists(batchRootDirectory))
            {
                Directory.CreateDirectory(batchRootDirectory);
            }

            changesSelected = new DatabaseChangesSelected();

            // Create a batch
            // batchinfo generate a schema clone with scope columns if needed
            batchInfo = new BatchInfo(scopeInfo.Schema, batchRootDirectory, batchDirectoryName);
            batchInfo.TryRemoveDirectory();
            batchInfo.CreateDirectory();

            // Call interceptor
            var databaseChangesSelectingArgs = new DatabaseChangesSelectingArgs(context, batchInfo.GetDirectoryFullPath(), this.Options.BatchSize, isNew,
                                                                                fromLastTimestamp, toNewTimestamp, connection, transaction);

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

            var cptSyncTable    = 0;
            var currentProgress = context.ProgressPercentage;

            var schemaTables = scopeInfo.Schema.Tables.SortByDependencies(tab => tab.GetRelations().Select(r => r.GetParentTable()));

            var lstAllBatchPartInfos    = new ConcurrentBag <BatchPartInfo>();
            var lstTableChangesSelected = new ConcurrentBag <TableChangesSelected>();

            var threadNumberLimits = supportsMultiActiveResultSets ? 16 : 1;

            if (supportsMultiActiveResultSets)
            {
                await schemaTables.ForEachAsync(async syncTable =>
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        return;
                    }

                    // tmp count of table for report progress pct
                    cptSyncTable++;

                    List <BatchPartInfo> syncTableBatchPartInfos;
                    TableChangesSelected tableChangesSelected;
                    (context, syncTableBatchPartInfos, tableChangesSelected) = await InternalReadSyncTableChangesAsync(
                        scopeInfo, context, excludingScopeId, syncTable, batchInfo, isNew, fromLastTimestamp, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

                    if (syncTableBatchPartInfos == null)
                    {
                        return;
                    }

                    // We don't report progress if no table changes is empty, to limit verbosity
                    if (tableChangesSelected != null && (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0))
                    {
                        lstTableChangesSelected.Add(tableChangesSelected);
                    }

                    // Add sync table bpi to all bpi
                    syncTableBatchPartInfos.ForEach(bpi => lstAllBatchPartInfos.Add(bpi));

                    context.ProgressPercentage = currentProgress + (cptSyncTable * 0.2d / scopeInfo.Schema.Tables.Count);
                }, threadNumberLimits);
            }
            else
            {
                foreach (var syncTable in schemaTables)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        continue;
                    }

                    // tmp count of table for report progress pct
                    cptSyncTable++;

                    List <BatchPartInfo> syncTableBatchPartInfos;
                    TableChangesSelected tableChangesSelected;
                    (context, syncTableBatchPartInfos, tableChangesSelected) = await InternalReadSyncTableChangesAsync(
                        scopeInfo, context, excludingScopeId, syncTable, batchInfo, isNew, fromLastTimestamp, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

                    if (syncTableBatchPartInfos == null)
                    {
                        continue;
                    }

                    // We don't report progress if no table changes is empty, to limit verbosity
                    if (tableChangesSelected != null && (tableChangesSelected.Deletes > 0 || tableChangesSelected.Upserts > 0))
                    {
                        lstTableChangesSelected.Add(tableChangesSelected);
                    }

                    // Add sync table bpi to all bpi
                    syncTableBatchPartInfos.ForEach(bpi => lstAllBatchPartInfos.Add(bpi));

                    context.ProgressPercentage = currentProgress + (cptSyncTable * 0.2d / scopeInfo.Schema.Tables.Count);
                }
            }

            while (!lstTableChangesSelected.IsEmpty)
            {
                if (lstTableChangesSelected.TryTake(out var tableChangesSelected))
                {
                    changesSelected.TableChangesSelected.Add(tableChangesSelected);
                }
            }


            // delete all empty batchparts (empty tables)
            foreach (var bpi in lstAllBatchPartInfos.Where(bpi => bpi.RowsCount <= 0))
            {
                File.Delete(Path.Combine(batchInfo.GetDirectoryFullPath(), bpi.FileName));
            }

            // Generate a good index order to be compliant with previous versions
            var tmpLstBatchPartInfos = new List <BatchPartInfo>();

            foreach (var table in schemaTables)
            {
                // get all bpi where count > 0 and ordered by index
                foreach (var bpi in lstAllBatchPartInfos.Where(bpi => bpi.RowsCount > 0 && bpi.Tables[0].EqualsByName(new BatchPartTableInfo(table.TableName, table.SchemaName))).OrderBy(bpi => bpi.Index).ToArray())
                {
                    batchInfo.BatchPartsInfo.Add(bpi);
                    batchInfo.RowsCount += bpi.RowsCount;

                    tmpLstBatchPartInfos.Add(bpi);
                }
            }

            var newBatchIndex = 0;

            foreach (var bpi in tmpLstBatchPartInfos)
            {
                bpi.Index = newBatchIndex;
                newBatchIndex++;
                bpi.IsLastBatch = newBatchIndex == tmpLstBatchPartInfos.Count;
            }

            //Set the total rows count contained in the batch info
            batchInfo.EnsureLastBatch();

            if (batchInfo.RowsCount <= 0)
            {
                var cleanFolder = await this.InternalCanCleanFolderAsync(scopeInfo.Name, context.Parameters, batchInfo, cancellationToken).ConfigureAwait(false);

                batchInfo.Clear(cleanFolder);
            }

            var databaseChangesSelectedArgs = new DatabaseChangesSelectedArgs(context, fromLastTimestamp, toNewTimestamp, batchInfo, changesSelected, connection);

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

            return(context, batchInfo, changesSelected);
        }
Пример #2
0
        /// <summary>
        /// Apply changes : Insert / Updates Delete
        /// 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, ChangesApplied)> ApplyChangesAsync(SyncContext context, ScopeInfo fromScope, BatchInfo changes)
        {
            ChangeApplicationAction changeApplicationAction;
            DbTransaction           applyTransaction = null;
            ChangesApplied          changesApplied   = new ChangesApplied();
            DbConnection            connection       = null;

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

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

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

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

                    // -----------------------------------------------------
                    // 1) Applying deletes. Do not apply deletes if we are in a new database
                    // -----------------------------------------------------
                    if (!fromScope.IsNewScope)
                    {
                        changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Deleted, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw new SyncException("Rollback during applying deletes", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                        }
                    }

                    // -----------------------------------------------------
                    // 1) Applying Inserts
                    // -----------------------------------------------------
                    changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Added, changesApplied);

                    // Rollback
                    if (changeApplicationAction == ChangeApplicationAction.Rollback)
                    {
                        throw new SyncException("Rollback during applying inserts", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                    }

                    // -----------------------------------------------------
                    // 1) Applying updates
                    // -----------------------------------------------------
                    changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Modified, changesApplied);

                    // Rollback
                    if (changeApplicationAction == ChangeApplicationAction.Rollback)
                    {
                        throw new SyncException("Rollback during applying updates", context.SyncStage, this.ProviderTypeName, SyncExceptionType.Rollback);
                    }

                    applyTransaction?.Commit();

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

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

                if (changes != null)
                {
                    changes.Clear();
                }
            }
        }
Пример #3
0
        /// <summary>
        /// Apply changes : Insert / Updates Delete
        /// 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, ChangesApplied)> ApplyChangesAsync(SyncContext context, ScopeInfo fromScope, BatchInfo changes)
        {
            try
            {
                ChangeApplicationAction changeApplicationAction;
                DbTransaction           applyTransaction = null;
                ChangesApplied          changesApplied   = new ChangesApplied();

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

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

                        Debug.WriteLine($"----- Applying Changes for Scope \"{fromScope.Name}\" -----");
                        Debug.WriteLine("");

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

                            // Rollback
                            if (changeApplicationAction == ChangeApplicationAction.Rollback)
                            {
                                throw SyncException.CreateRollbackException(context.SyncStage);
                            }
                        }

                        // -----------------------------------------------------
                        // 1) Applying deletes. Do not apply deletes if we are in a new database
                        // -----------------------------------------------------
                        if (!fromScope.IsNewScope)
                        {
                            changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Deleted, changesApplied);

                            // Rollback
                            if (changeApplicationAction == ChangeApplicationAction.Rollback)
                            {
                                throw SyncException.CreateRollbackException(context.SyncStage);
                            }
                        }

                        // -----------------------------------------------------
                        // 1) Applying Inserts
                        // -----------------------------------------------------
                        changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Added, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw SyncException.CreateRollbackException(context.SyncStage);
                        }

                        // -----------------------------------------------------
                        // 1) Applying updates
                        // -----------------------------------------------------
                        changeApplicationAction = this.ApplyChangesInternal(context, connection, applyTransaction, fromScope, changes, DmRowState.Modified, changesApplied);

                        // Rollback
                        if (changeApplicationAction == ChangeApplicationAction.Rollback)
                        {
                            throw SyncException.CreateRollbackException(context.SyncStage);
                        }

                        applyTransaction.Commit();

                        Debug.WriteLine($"--- End Applying Changes for Scope \"{fromScope.Name}\" ---");
                        Debug.WriteLine("");
                    }
                    catch (Exception exception)
                    {
                        Debug.WriteLine($"Caught exception while applying changes: {exception}");
                        throw;
                    }
                    finally
                    {
                        if (applyTransaction != null)
                        {
                            applyTransaction.Dispose();
                            applyTransaction = null;
                        }

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

                        if (changes != null)
                        {
                            changes.Clear();
                        }
                    }
                    return(context, changesApplied);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }