コード例 #1
0
        /// <summary>
        /// Write scope in the provider datasource
        /// </summary>
        public virtual async Task <SyncContext> WriteScopesAsync(SyncContext context, List <ScopeInfo> scopes)
        {
            // Open the connection
            using (var connection = this.CreateConnection())
            {
                try
                {
                    await connection.OpenAsync();

                    using (var transaction = connection.BeginTransaction())
                    {
                        var scopeBuilder     = this.GetScopeBuilder();
                        var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(connection, transaction);

                        var lstScopes = new List <ScopeInfo>();

                        foreach (var scope in scopes)
                        {
                            lstScopes.Add(scopeInfoBuilder.InsertOrUpdateScopeInfo(scope));
                        }

                        context.SyncStage = SyncStage.ScopeSaved;

                        // Event progress
                        this.TryRaiseProgressEvent(
                            new ScopeEventArgs(this.ProviderTypeName, context.SyncStage,
                                               lstScopes.FirstOrDefault(s => s.IsLocal)), ScopeSaved);

                        transaction.Commit();
                    }
                }
                catch (DbException dbex)
                {
                    throw SyncException.CreateDbException(context.SyncStage, dbex);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);

                    if (ex is SyncException)
                    {
                        throw;
                    }
                    else
                    {
                        throw SyncException.CreateUnknowException(context.SyncStage, ex);
                    }

                    throw;
                }
                finally
                {
                    if (connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }
                }
                return(context);
            }
        }
コード例 #2
0
        internal static SyncException CreateArgumentException(SyncStage syncStage, string paramName, string message = null)
        {
            var           m             = message ?? $"Argument exception on parameter {paramName}";
            SyncException syncException = new SyncException(m, syncStage, SyncExceptionType.Argument);

            syncException.Argument = paramName;
            return(syncException);
        }
コード例 #3
0
        /// <summary>
        /// Encapsulates an error in a SyncException, let provider enrich the error if needed, then throw again
        /// </summary>
        internal void RaiseError(Exception exception)
        {
            var syncException = new SyncException(exception, this.GetContext().SyncStage);

            // try to let the provider enrich the exception
            this.Provider.EnsureSyncException(syncException);
            syncException.Side = this.Side;

            this.logger.LogError(SyncEventsId.Exception, syncException, syncException.Message);

            throw syncException;
        }
コード例 #4
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>
        public virtual async Task <(SyncContext, BatchInfo, ChangesSelected)> GetChangeBatchAsync(SyncContext context, ScopeInfo scopeInfo)
        {
            try
            {
                if (scopeInfo == null)
                {
                    throw new ArgumentException("ClientScope is null");
                }

                var configuration = GetCacheConfiguration();

                // check batchSize if not > then Configuration.DownloadBatchSizeInKB
                if (configuration.DownloadBatchSizeInKB > 0)
                {
                    Debug.WriteLine($"Enumeration data cache size selected: {configuration.DownloadBatchSizeInKB} Kb");
                }

                // bacth info containing changes
                BatchInfo batchInfo;

                // Statistics about changes that are selected
                ChangesSelected changesSelected;

                (context, batchInfo, changesSelected) = await this.GetChanges(context, scopeInfo);

                // Check if the remote is not outdated
                var isOutdated = this.IsRemoteOutdated();

                if (isOutdated)
                {
                    throw new Exception("OutDatedPeer");
                }

                return(context, batchInfo, changesSelected);
            }
            catch (DbException dbex)
            {
                throw SyncException.CreateDbException(context.SyncStage, dbex);
            }
            catch (Exception ex)
            {
                if (ex is SyncException)
                {
                    throw;
                }
                else
                {
                    throw SyncException.CreateUnknowException(context.SyncStage, ex);
                }
            }
        }
コード例 #5
0
ファイル: CoreProvider.cs プロジェクト: aliusman/Dotmim.Sync
        /// <summary>
        /// Try to raise a generalist progress event
        /// </summary>
        private void TryRaiseProgressEvent(SyncStage stage, String message, Dictionary <String, String> properties = null)
        {
            ProgressEventArgs progressEventArgs = new ProgressEventArgs(this.ProviderTypeName, stage, message);

            if (properties != null)
            {
                progressEventArgs.Properties = properties;
            }

            SyncProgress?.Invoke(this, progressEventArgs);

            if (progressEventArgs.Action == ChangeApplicationAction.Rollback)
            {
                throw SyncException.CreateRollbackException(progressEventArgs.Stage);
            }
        }
コード例 #6
0
ファイル: SyncAgent.cs プロジェクト: tbolon/Dotmim.Sync
        private static void HandleSyncError(SyncException sex)
        {
            switch (sex.SyncStage)
            {
            case SyncStage.BeginSession:
                Logger.Current.Info(sex.ToString());
                break;

            case SyncStage.EnsureConfiguration:
                break;

            case SyncStage.SelectedChanges:
                break;

            case SyncStage.ApplyingChanges:
                break;

            case SyncStage.AppliedChanges:
                break;

            case SyncStage.ApplyingInserts:
                break;

            case SyncStage.ApplyingUpdates:
                break;

            case SyncStage.ApplyingDeletes:
                break;

            case SyncStage.WriteMetadata:
                break;

            case SyncStage.EndSession:
                Logger.Current.Info(sex.ToString());
                break;

            case SyncStage.CleanupMetadata:
                break;

            default:
                break;
            }

            // try to end sessions on both
        }
コード例 #7
0
ファイル: CoreProvider.cs プロジェクト: aliusman/Dotmim.Sync
        /// <summary>
        /// Called when the sync is over
        /// </summary>
        public virtual Task <SyncContext> EndSessionAsync(SyncContext context)
        {
            try
            {
                // already ended
                lock (this)
                {
                    if (!syncInProgress)
                    {
                        return(Task.FromResult(context));
                    }
                }

                Debug.WriteLine($"EndSession() called on Provider {this.ProviderTypeName}");

                context.SyncStage = SyncStage.EndSession;

                // Event progress
                this.TryRaiseProgressEvent(
                    new EndSessionEventArgs(this.ProviderTypeName, context.SyncStage), this.EndSession);
            }
            catch (Exception ex)
            {
                if (ex is SyncException)
                {
                    throw;
                }
                else
                {
                    throw SyncException.CreateUnknowException(context.SyncStage, ex);
                }
            }

            finally
            {
                lock (this) { this.syncInProgress = false; }
            }

            return(Task.FromResult(context));
        }
コード例 #8
0
ファイル: CoreProvider.cs プロジェクト: aliusman/Dotmim.Sync
        /// <summary>
        /// Read a scope info
        /// </summary>
        public virtual async Task <(SyncContext, long)> GetLocalTimestampAsync(SyncContext context)
        {
            // Open the connection
            using (var connection = this.CreateConnection())
            {
                try
                {
                    await connection.OpenAsync();

                    var scopeBuilder     = this.GetScopeBuilder();
                    var scopeInfoBuilder = scopeBuilder.CreateScopeInfoBuilder(connection);
                    var localTime        = scopeInfoBuilder.GetLocalTimestamp();
                    return(context, localTime);
                }
                catch (DbException dbex)
                {
                    throw SyncException.CreateDbException(context.SyncStage, dbex);
                }
                catch (Exception ex)
                {
                    if (ex is SyncException)
                    {
                        throw;
                    }
                    else
                    {
                        throw SyncException.CreateUnknowException(context.SyncStage, ex);
                    }
                }
                finally
                {
                    if (connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }
                }
            }
        }
コード例 #9
0
ファイル: CoreProvider.cs プロジェクト: aliusman/Dotmim.Sync
        /// <summary>
        /// Called by the  to indicate that a
        /// synchronization session has started.
        /// </summary>
        public virtual Task <SyncContext> BeginSessionAsync(SyncContext context)
        {
            try
            {
                Debug.WriteLine($"BeginSession() called on Provider {this.ProviderTypeName}");

                lock (this)
                {
                    if (this.syncInProgress)
                    {
                        throw SyncException.CreateInProgressException(context.SyncStage);
                    }

                    this.syncInProgress = true;
                }

                // Set stage
                context.SyncStage = SyncStage.BeginSession;

                // Event progress
                var progressEventArgs = new BeginSessionEventArgs(this.ProviderTypeName, context.SyncStage);
                this.TryRaiseProgressEvent(progressEventArgs, this.BeginSession);
            }
            catch (Exception ex)
            {
                if (ex is SyncException)
                {
                    throw;
                }
                else
                {
                    throw SyncException.CreateUnknowException(context.SyncStage, ex);
                }
            }
            return(Task.FromResult(context));
        }
コード例 #10
0
        internal static Exception CreateInProgressException(SyncStage syncStage)
        {
            SyncException syncException = new SyncException("Session already in progress", syncStage, SyncExceptionType.Rollback);

            return(syncException);
        }
コード例 #11
0
        internal static SyncException CreateUnknowException(SyncStage stage, Exception ex)
        {
            SyncException syncException = new SyncException("Unknown error has occured", stage, ex, SyncExceptionType.Unknown);

            return(syncException);
        }
コード例 #12
0
        /// <summary>
        /// Create a rollback exception to rollback the Sync session
        /// </summary>
        /// <param name="context"></param>
        internal static SyncException CreateRollbackException(SyncStage stage)
        {
            SyncException syncException = new SyncException("User rollback action.", stage, SyncExceptionType.Rollback);

            return(syncException);
        }
コード例 #13
0
        internal static SyncException CreateDbException(SyncStage syncStage, DbException dbex)
        {
            SyncException syncException = new SyncException(dbex.Message, syncStage, dbex, SyncExceptionType.DataStore);

            return(syncException);
        }
コード例 #14
0
        /// <summary>
        /// Deprovision a database. You have to passe a configuration object, containing at least the dmTables
        /// </summary>
        public async Task DeprovisionAsync(string[] tables, SyncProvision provision)
        {
            if (tables == null || tables.Length == 0)
            {
                throw new SyncException("You must set the tables you want to modify");
            }

            // Load the configuration
            var configuration = await this.ReadConfigurationAsync(tables);

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

                    using (var transaction = connection.BeginTransaction())
                    {
                        for (int i = configuration.Count - 1; i >= 0; i--)
                        {
                            // Get the table
                            var dmTable = configuration[i];

                            // get the builder
                            var builder = GetDatabaseBuilder(dmTable);

                            // adding filters
                            this.AddFilters(configuration, dmTable, builder);

                            if (provision.HasFlag(SyncProvision.TrackingTable) || provision == SyncProvision.All)
                            {
                                builder.DropTrackingTable(connection, transaction);
                            }

                            if (provision.HasFlag(SyncProvision.StoredProcedures) || provision == SyncProvision.All)
                            {
                                builder.DropProcedures(connection, transaction);
                            }

                            if (provision.HasFlag(SyncProvision.Triggers) || provision == SyncProvision.All)
                            {
                                builder.DropTriggers(connection, transaction);
                            }

                            if (provision.HasFlag(SyncProvision.Table) || provision == SyncProvision.All)
                            {
                                builder.DropTable(connection, transaction);
                            }
                        }


                        transaction.Commit();
                    }
                }
                catch (Exception ex)
                {
                    throw SyncException.CreateUnknowException(SyncStage.BeginSession, ex);
                }
                finally
                {
                    if (connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }
                }
            }
        }
コード例 #15
0
        internal static SyncException CreateNotSupportedException(SyncStage syncStage, string notSupportedMessage)
        {
            SyncException syncException = new SyncException(notSupportedMessage, syncStage, SyncExceptionType.NotSupported);

            return(syncException);
        }
コード例 #16
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;
            }
        }
コード例 #17
0
 /// <summary>
 /// Let a chance to provider to enrich SyncExecption
 /// </summary>
 public virtual void EnsureSyncException(SyncException syncException)
 {
 }
コード例 #18
0
        /// <summary>
        /// Called when the sync ensure scopes are created
        /// </summary>
        public virtual async Task <(SyncContext, List <ScopeInfo>)> EnsureScopesAsync(SyncContext context, string scopeName, Guid?clientReferenceId = null)
        {
            try
            {
                if (string.IsNullOrEmpty(scopeName))
                {
                    throw SyncException.CreateArgumentException(SyncStage.ScopeSaved, "ScopeName");
                }

                context.SyncStage = SyncStage.ScopeLoading;

                List <ScopeInfo> scopes = new List <ScopeInfo>();

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

                        using (var transaction = connection.BeginTransaction())
                        {
                            var scopeBuilder               = this.GetScopeBuilder();
                            var scopeInfoBuilder           = scopeBuilder.CreateScopeInfoBuilder(connection, transaction);
                            var needToCreateScopeInfoTable = scopeInfoBuilder.NeedToCreateScopeInfoTable();

                            // create the scope info table if needed
                            if (needToCreateScopeInfoTable)
                            {
                                scopeInfoBuilder.CreateScopeInfoTable();
                            }

                            // not the first time we ensure scopes, so get scopes
                            if (!needToCreateScopeInfoTable)
                            {
                                // get all scopes shared by all (identified by scopeName)
                                var lstScopes = scopeInfoBuilder.GetAllScopes(scopeName);

                                // try to get the scopes from database
                                // could be two scopes if from server or a single scope if from client
                                scopes = lstScopes.Where(s => (s.IsLocal == true || (clientReferenceId.HasValue && s.Id == clientReferenceId.Value))).ToList();
                            }

                            // If no scope found, create it on the local provider
                            if (scopes == null || scopes.Count <= 0)
                            {
                                scopes = new List <ScopeInfo>();

                                // create a new scope id for the current owner (could be server or client as well)
                                var scope = new ScopeInfo();
                                scope.Id         = Guid.NewGuid();
                                scope.Name       = scopeName;
                                scope.IsLocal    = true;
                                scope.IsNewScope = true;
                                scope.LastSync   = null;

                                scope = scopeInfoBuilder.InsertOrUpdateScopeInfo(scope);

                                scopes.Add(scope);
                            }
                            else
                            {
                                //check if we have alread a good last sync. if no, treat it as new
                                scopes.ForEach(sc => sc.IsNewScope = sc.LastSync == null);
                            }

                            // if we are not on the server, we have to check that we only have one scope
                            if (!clientReferenceId.HasValue && scopes.Count > 1)
                            {
                                throw SyncException.CreateNotSupportedException(SyncStage.ScopeSaved, "On Local provider, we should have only one scope info");
                            }


                            // if we have a reference in args, we need to get this specific line from database
                            // this happen only on the server side
                            if (clientReferenceId.HasValue)
                            {
                                var refScope = scopes.FirstOrDefault(s => s.Id == clientReferenceId);

                                if (refScope == null)
                                {
                                    refScope            = new ScopeInfo();
                                    refScope.Id         = clientReferenceId.Value;
                                    refScope.Name       = scopeName;
                                    refScope.IsLocal    = false;
                                    refScope.IsNewScope = true;
                                    refScope.LastSync   = null;

                                    refScope = scopeInfoBuilder.InsertOrUpdateScopeInfo(refScope);

                                    scopes.Add(refScope);
                                }
                                else
                                {
                                    refScope.IsNewScope = refScope.LastSync == null;
                                }
                            }

                            transaction.Commit();
                        }
                    }
                    catch (DbException dbex)
                    {
                        throw SyncException.CreateDbException(context.SyncStage, dbex);
                    }
                    catch (Exception dbex)
                    {
                        throw SyncException.CreateUnknowException(context.SyncStage, dbex);
                    }
                    finally
                    {
                        if (connection.State != ConnectionState.Closed)
                        {
                            connection.Close();
                        }
                    }
                }

                // Event progress
                this.TryRaiseProgressEvent(
                    new ScopeEventArgs(this.ProviderTypeName, context.SyncStage, scopes.FirstOrDefault(s => s.IsLocal)), ScopeLoading);

                return(context, scopes);
            }
            catch (Exception ex)
            {
                if (ex is SyncException)
                {
                    throw;
                }
                else
                {
                    throw SyncException.CreateUnknowException(context.SyncStage, ex);
                }
            }
        }
コード例 #19
0
        /// <summary>
        /// Generate the DmTable configuration from a given columns list
        /// Validate that all columns are currently supported by the provider
        /// </summary>
        private void ValidateTableFromColumnsList(DmTable dmTable, List <DmColumn> columns, IDbManagerTable dbManagerTable)
        {
            dmTable.OriginalProvider = this.ProviderTypeName;

            var ordinal = 0;

            if (columns == null || columns.Count <= 0)
            {
                throw new SyncException($"{dmTable.TableName} does not contains any columns.", SyncStage.ConfigurationApplying, SyncExceptionType.NotSupported);
            }

            // Get PrimaryKey
            var dmTableKeys = dbManagerTable.GetTablePrimaryKeys();

            if (dmTableKeys == null || dmTableKeys.Count == 0)
            {
                throw new SyncException($"No Primary Keys in table {dmTable.TableName}, Can't make a synchronization with a table without primary keys.", SyncStage.ConfigurationApplying, SyncExceptionType.NoPrimaryKeys);
            }

            // Check if we have more than one column (excepting primarykeys)
            var columnsNotPkeys = columns.Count(c => !dmTableKeys.Contains(c.ColumnName));

            if (columnsNotPkeys <= 0)
            {
                throw new SyncException($"{dmTable.TableName} does not contains any columns, excepting primary keys.", SyncStage.ConfigurationApplying, SyncExceptionType.NotSupported);
            }

            foreach (var column in columns.OrderBy(c => c.Ordinal))
            {
                // First of all validate if the column is currently supported
                if (!Metadata.IsValid(column))
                {
                    throw SyncException.CreateNotSupportedException(
                              SyncStage.ConfigurationApplying, $"The Column {column.ColumnName} of type {column.OriginalTypeName} from provider {this.ProviderTypeName} is not currently supported.");
                }

                var columnNameLower = column.ColumnName.ToLowerInvariant();
                if (columnNameLower == "sync_scope_name" ||
                    columnNameLower == "scope_timestamp" ||
                    columnNameLower == "scope_is_local" ||
                    columnNameLower == "scope_last_sync" ||
                    columnNameLower == "create_scope_id" ||
                    columnNameLower == "update_scope_id" ||
                    columnNameLower == "create_timestamp" ||
                    columnNameLower == "update_timestamp" ||
                    columnNameLower == "timestamp" ||
                    columnNameLower == "sync_row_is_tombstone" ||
                    columnNameLower == "last_change_datetime" ||
                    columnNameLower == "sync_scope_name" ||
                    columnNameLower == "sync_scope_name"
                    )
                {
                    throw SyncException.CreateNotSupportedException(
                              SyncStage.ConfigurationApplying, $"The Column name {column.ColumnName} from provider {this.ProviderTypeName} is a reserved column name. Please choose another column name.");
                }

                dmTable.Columns.Add(column);

                // Gets the datastore owner dbType (could be SqlDbtype, MySqlDbType, SqliteDbType, NpgsqlDbType & so on ...)
                object datastoreDbType = Metadata.ValidateOwnerDbType(column.OriginalTypeName, column.IsUnsigned, column.IsUnicode);

                // once we have the datastore type, we can have the managed type
                Type columnType = Metadata.ValidateType(datastoreDbType);

                // and the DbType
                column.DbType = Metadata.ValidateDbType(column.OriginalTypeName, column.IsUnsigned, column.IsUnicode);

                // Gets the owner dbtype (SqlDbType, OracleDbType, MySqlDbType, NpsqlDbType & so on ...)
                // Sqlite does not have it's own type, so it's DbType too
                column.OriginalDbType = datastoreDbType.ToString();

                // Validate max length
                column.MaxLength = Metadata.ValidateMaxLength(column.OriginalTypeName, column.IsUnsigned, column.IsUnicode, column.MaxLength);

                // Validate if column should be readonly
                column.ReadOnly = Metadata.ValidateIsReadonly(column);

                // set position ordinal
                column.SetOrdinal(ordinal);
                ordinal++;

                // Validate the precision and scale properties
                if (Metadata.IsNumericType(column.OriginalTypeName))
                {
                    if (Metadata.SupportScale(column.OriginalTypeName))
                    {
                        var(p, s)                 = Metadata.ValidatePrecisionAndScale(column);
                        column.Precision          = p;
                        column.PrecisionSpecified = true;
                        column.Scale              = s;
                        column.ScaleSpecified     = true;
                    }
                    else
                    {
                        column.Precision          = Metadata.ValidatePrecision(column);
                        column.PrecisionSpecified = true;
                        column.ScaleSpecified     = false;
                    }
                }
            }

            DmColumn[] columnsForKey = new DmColumn[dmTableKeys.Count];

            for (int i = 0; i < dmTableKeys.Count; i++)
            {
                var rowColumn = dmTableKeys[i];
                var columnKey = dmTable.Columns.FirstOrDefault(c => String.Equals(c.ColumnName, rowColumn, StringComparison.InvariantCultureIgnoreCase));
                columnsForKey[i] = columnKey ?? throw new SyncException("Primary key found is not present in the columns list", SyncStage.ConfigurationApplying, SyncExceptionType.NoPrimaryKeys);
            }

            // Set the primary Key
            dmTable.PrimaryKey = new DmKey(columnsForKey);
        }
コード例 #20
0
        internal static SyncException CreateOperationCanceledException(SyncStage syncStage, OperationCanceledException oce)
        {
            SyncException syncException = new SyncException("Operation canceled.", syncStage, SyncExceptionType.OperationCanceled);

            return(syncException);
        }
コード例 #21
0
        /// <summary>
        /// Ensure configuration is correct on both server and client side
        /// </summary>
        public virtual async Task <(SyncContext, SyncConfiguration)> EnsureConfigurationAsync(SyncContext context,
                                                                                              SyncConfiguration configuration = null)
        {
            try
            {
                context.SyncStage = SyncStage.ConfigurationApplying;

                // Get cache manager and try to get configuration from cache
                var cacheManager       = this.CacheManager;
                var cacheConfiguration = GetCacheConfiguration();

                // if we don't pass config object (configuration == null), we may be in proxy mode, so the config object is handled by a local configuration object.
                if (configuration == null && this.syncConfiguration == null)
                {
                    throw SyncException.CreateArgumentException(SyncStage.ConfigurationApplied, "Configuration", "You try to set a provider with no configuration object");
                }

                // the configuration has been set from the proxy server itself, use it.
                if (configuration == null && this.syncConfiguration != null)
                {
                    configuration = this.syncConfiguration;
                }

                // Raise event before
                context.SyncStage = SyncStage.ConfigurationApplying;
                var beforeArgs2 = new ConfigurationApplyingEventArgs(this.ProviderTypeName, context.SyncStage);
                this.TryRaiseProgressEvent(beforeArgs2, this.ConfigurationApplying);
                bool overWriteConfiguration = beforeArgs2.OverwriteConfiguration;

                // if we have already a cache configuration, we can return, except if we should overwrite it
                if (cacheConfiguration != null && !overWriteConfiguration)
                {
                    // Raise event after
                    context.SyncStage = SyncStage.ConfigurationApplied;
                    var afterArgs2 = new ConfigurationAppliedEventArgs(this.ProviderTypeName, context.SyncStage, cacheConfiguration);
                    this.TryRaiseProgressEvent(afterArgs2, this.ConfigurationApplied);
                    // if config has been changed by user, save it again
                    this.SetCacheConfiguration(cacheConfiguration);
                    return(context, cacheConfiguration);
                }

                // create local directory
                if (!String.IsNullOrEmpty(configuration.BatchDirectory) && !Directory.Exists(configuration.BatchDirectory))
                {
                    Directory.CreateDirectory(configuration.BatchDirectory);
                }

                // if we dont have already read the tables || we want to overwrite the current config
                if ((configuration.HasTables && !configuration.HasColumns))
                {
                    string[] tables = new string[configuration.Count];

                    for (int i = 0; i < configuration.Count; i++)
                    {
                        // just check if we have a schema
                        var dmTable   = configuration[i];
                        var tableName = String.IsNullOrEmpty(dmTable.Schema) ? dmTable.TableName : $"[{dmTable.Schema}].[{dmTable.TableName}]";
                        tables[i] = tableName;
                    }

                    await this.ReadConfigurationAsync(configuration);
                }

                // save to cache
                this.SetCacheConfiguration(configuration);

                context.SyncStage = SyncStage.ConfigurationApplied;
                var afterArgs = new ConfigurationAppliedEventArgs(this.ProviderTypeName, context.SyncStage, configuration);
                this.TryRaiseProgressEvent(afterArgs, this.ConfigurationApplied);
                // if config has been changed by user, save it again
                this.SetCacheConfiguration(configuration);
                return(context, configuration);
            }
            catch (Exception ex)
            {
                if (ex is SyncException)
                {
                    throw;
                }
                else
                {
                    throw SyncException.CreateUnknowException(context.SyncStage, ex);
                }
            }
        }
コード例 #22
0
        /// <summary>
        /// Be sure all tables are ready and configured for sync
        /// the ScopeSet Configuration MUST be filled by the schema form Database
        /// </summary>
        public virtual async Task <SyncContext> EnsureDatabaseAsync(SyncContext context, ScopeInfo scopeInfo)
        {
            var configuration = GetCacheConfiguration();

            // Event progress
            context.SyncStage = SyncStage.DatabaseApplying;
            DatabaseApplyingEventArgs beforeArgs =
                new DatabaseApplyingEventArgs(this.ProviderTypeName, context.SyncStage, configuration);

            this.TryRaiseProgressEvent(beforeArgs, this.DatabaseApplying);

            // if config has been editer by user in event, save again
            this.SetCacheConfiguration(configuration);

            // If scope exists and lastdatetime sync is present, so database exists
            // Check if we don't have an OverwriteConfiguration (if true, we force the check)
            if (scopeInfo.LastSync.HasValue && !beforeArgs.OverwriteConfiguration)
            {
                return(context);
            }

            StringBuilder script = new StringBuilder();

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

                    using (var transaction = connection.BeginTransaction())
                    {
                        foreach (var dmTable in configuration)
                        {
                            var builder = GetDatabaseBuilder(dmTable);

                            // adding filter
                            this.AddFilters(configuration, dmTable, builder);

                            context.SyncStage = SyncStage.DatabaseTableApplying;
                            DatabaseTableApplyingEventArgs beforeTableArgs =
                                new DatabaseTableApplyingEventArgs(this.ProviderTypeName, context.SyncStage, dmTable.TableName);
                            this.TryRaiseProgressEvent(beforeTableArgs, this.DatabaseTableApplying);

                            string currentScript = null;
                            if (beforeArgs.GenerateScript)
                            {
                                currentScript  = builder.ScriptTable(connection, transaction);
                                currentScript += builder.ScriptForeignKeys(connection, transaction);
                                script.Append(currentScript);
                            }

                            builder.Create(connection, transaction);
                            builder.CreateForeignKeys(connection, transaction);

                            context.SyncStage = SyncStage.DatabaseTableApplied;
                            DatabaseTableAppliedEventArgs afterTableArgs =
                                new DatabaseTableAppliedEventArgs(this.ProviderTypeName, context.SyncStage, dmTable.TableName, currentScript);
                            this.TryRaiseProgressEvent(afterTableArgs, this.DatabaseTableApplied);
                        }

                        context.SyncStage = SyncStage.DatabaseApplied;
                        var afterArgs = new DatabaseAppliedEventArgs(this.ProviderTypeName, context.SyncStage, script.ToString());
                        this.TryRaiseProgressEvent(afterArgs, this.DatabaseApplied);

                        transaction.Commit();
                    }
                }
                catch (DbException dbex)
                {
                    throw SyncException.CreateDbException(context.SyncStage, dbex);
                }
                catch (Exception ex)
                {
                    if (ex is SyncException)
                    {
                        throw;
                    }
                    else
                    {
                        throw SyncException.CreateUnknowException(context.SyncStage, ex);
                    }
                }
                finally
                {
                    if (connection.State != ConnectionState.Closed)
                    {
                        connection.Close();
                    }
                }
                return(context);
            }
        }
コード例 #23
0
ファイル: SyncAgent.cs プロジェクト: tbolon/Dotmim.Sync
        /// <summary>
        /// Launch a synchronization with the specified mode
        /// </summary>
        public async Task <SyncContext> SynchronizeAsync(SyncType syncType, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(this.scopeName))
            {
                throw new Exception("Scope Name is mandatory");
            }

            // Context, used to back and forth data between servers
            SyncContext context = new SyncContext(Guid.NewGuid());

            // set start time
            context.StartTime = DateTime.Now;

            // if any parameters, set in context
            context.Parameters = this.Parameters;

            context.SyncType = syncType;

            this.SessionState = SyncSessionState.Synchronizing;
            this.SessionStateChanged?.Invoke(this, this.SessionState);
            ScopeInfo localScopeInfo = null, serverScopeInfo = null, localScopeReferenceInfo = null, scope = null;

            Guid fromId = Guid.Empty;
            long lastSyncTS = 0L;
            bool isNew  = true;

            // Stats computed
            ChangesStatistics changesStatistics = new ChangesStatistics();

            // tmp check error
            bool hasErrors = false;

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

                // Setting the cancellation token
                this.LocalProvider.SetCancellationToken(cancellationToken);
                this.RemoteProvider.SetCancellationToken(cancellationToken);

                // Begin Session / Read the adapters
                context = await this.RemoteProvider.BeginSessionAsync(context);

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

                context = await this.LocalProvider.BeginSessionAsync(context);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
                // ----------------------------------------
                // 1) Read scope info
                // ----------------------------------------

                // get the scope from local provider
                List <ScopeInfo> localScopes;
                List <ScopeInfo> serverScopes;
                (context, localScopes) = await this.LocalProvider.EnsureScopesAsync(context, scopeName);

                if (localScopes.Count != 1)
                {
                    throw new Exception("On Local provider, we should have only one scope info");
                }

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

                localScopeInfo = localScopes[0];

                (context, serverScopes) = await this.RemoteProvider.EnsureScopesAsync(context, scopeName, localScopeInfo.Id);

                if (serverScopes.Count != 2)
                {
                    throw new Exception("On Remote provider, we should have two scopes (one for server and one for client side)");
                }

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

                serverScopeInfo         = serverScopes.First(s => s.Id != localScopeInfo.Id);
                localScopeReferenceInfo = serverScopes.First(s => s.Id == localScopeInfo.Id);

                // ----------------------------------------
                // 2) Build Configuration Object
                // ----------------------------------------

                // Get Configuration from remote provider
                (context, this.Configuration) = await this.RemoteProvider.EnsureConfigurationAsync(context, this.Configuration);

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

                // Invert policy on the client
                var configurationLocale = this.Configuration.Clone();
                var policy = this.Configuration.ConflictResolutionPolicy;
                if (policy == ConflictResolutionPolicy.ServerWins)
                {
                    configurationLocale.ConflictResolutionPolicy = ConflictResolutionPolicy.ClientWins;
                }
                if (policy == ConflictResolutionPolicy.ClientWins)
                {
                    configurationLocale.ConflictResolutionPolicy = ConflictResolutionPolicy.ServerWins;
                }

                // Apply on local Provider
                SyncConfiguration configuration;
                (context, configuration) = await this.LocalProvider.EnsureConfigurationAsync(context, configurationLocale);

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

                // ----------------------------------------
                // 3) Ensure databases are ready
                // ----------------------------------------

                // Server should have already the schema
                context = await this.RemoteProvider.EnsureDatabaseAsync(context, serverScopeInfo, DbBuilderOption.CreateOrUseExistingSchema | DbBuilderOption.CreateOrUseExistingTrackingTables);

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

                // Client could have, or not, the tables
                context = await this.LocalProvider.EnsureDatabaseAsync(context, localScopeInfo, DbBuilderOption.CreateOrUseExistingSchema | DbBuilderOption.CreateOrUseExistingTrackingTables);

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

                // ----------------------------------------
                // 5) Get changes and apply them
                // ----------------------------------------
                BatchInfo         clientBatchInfo;
                BatchInfo         serverBatchInfo;
                ChangesStatistics clientStatistics    = null;
                ChangesStatistics serverStatistics    = null;
                ChangesStatistics tmpClientStatistics = null;
                ChangesStatistics tmpServerStatistics = null;

                // fromId : not really needed on this case, since updated / inserted / deleted row has marked null
                // otherwise, lines updated by server or others clients are already syncked
                fromId = localScopeInfo.Id;
                // lastSyncTS : get lines inserted / updated / deteleted after the last sync commited
                lastSyncTS = localScopeInfo.LastTimestamp;
                // isNew : If isNew, lasttimestamp is not correct, so grab all
                isNew = localScopeInfo.IsNewScope;

                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };
                (context, clientBatchInfo, clientStatistics) = await this.LocalProvider.GetChangeBatchAsync(context, scope);

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

                // Apply on the Server Side
                // Since we are on the server,
                // we need to check the server client timestamp (not the client timestamp which is completely different)

                // fromId : When applying rows, make sure it's identified as applied by this client scope
                fromId = localScopeInfo.Id;
                // lastSyncTS : apply lines only if thye are not modified since last client sync
                lastSyncTS = localScopeReferenceInfo.LastTimestamp;
                // isNew : not needed
                isNew = false;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };

                (context, serverStatistics) = await this.RemoteProvider.ApplyChangesAsync(context, scope, clientBatchInfo);

                if (cancellationToken.IsCancellationRequested)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                }
                // Get changes from server

                // fromId : Make sure we don't select lines on server that has been already updated by the client
                fromId = localScopeInfo.Id;
                // lastSyncTS : apply lines only if thye are not modified since last client sync
                lastSyncTS = localScopeReferenceInfo.LastTimestamp;
                // isNew : make sure we take all lines if it's the first time we get
                isNew = localScopeReferenceInfo.IsNewScope;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };

                (context, serverBatchInfo, tmpServerStatistics) = await this.RemoteProvider.GetChangeBatchAsync(context, scope);

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

                // Update server stats
                if (serverStatistics == null)
                {
                    serverStatistics = tmpServerStatistics;
                }
                else
                {
                    clientStatistics.SelectedChanges = tmpServerStatistics.SelectedChanges;
                }

                // Apply local changes

                // fromId : When applying rows, make sure it's identified as applied by this server scope
                fromId = serverScopeInfo.Id;
                // lastSyncTS : apply lines only if they are not modified since last client sync
                lastSyncTS = localScopeInfo.LastTimestamp;
                // isNew : if IsNew, don't apply deleted rows from server
                isNew = localScopeInfo.IsNewScope;
                scope = new ScopeInfo {
                    Id = fromId, IsNewScope = isNew, LastTimestamp = lastSyncTS
                };

                (context, tmpClientStatistics) = await this.LocalProvider.ApplyChangesAsync(context, scope, serverBatchInfo);

                if (clientStatistics == null)
                {
                    clientStatistics = tmpClientStatistics;
                }
                else
                {
                    clientStatistics.AppliedChanges = tmpClientStatistics.AppliedChanges;
                }

                context.TotalChangesDownloaded = clientStatistics.TotalAppliedChanges;
                context.TotalChangesUploaded   = serverStatistics.TotalAppliedChanges;
                context.TotalSyncErrors        = clientStatistics.TotalAppliedChangesFailed;

                long serverTimestamp, clientTimestamp;

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

                (context, serverTimestamp) = await this.RemoteProvider.GetLocalTimestampAsync(context);

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

                (context, clientTimestamp) = await this.LocalProvider.GetLocalTimestampAsync(context);

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

                context.CompleteTime = DateTime.Now;

                serverScopeInfo.IsNewScope         = false;
                localScopeReferenceInfo.IsNewScope = false;
                localScopeInfo.IsNewScope          = false;

                serverScopeInfo.LastSync         = context.CompleteTime;
                localScopeReferenceInfo.LastSync = context.CompleteTime;
                localScopeInfo.LastSync          = context.CompleteTime;

                serverScopeInfo.IsLocal         = true;
                localScopeReferenceInfo.IsLocal = false;

                context = await this.RemoteProvider.WriteScopesAsync(context, new List <ScopeInfo> {
                    serverScopeInfo, localScopeReferenceInfo
                });

                serverScopeInfo.IsLocal = false;
                localScopeInfo.IsLocal  = true;

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

                context = await this.LocalProvider.WriteScopesAsync(context, new List <ScopeInfo> {
                    localScopeInfo, serverScopeInfo
                });

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

                // Begin Session / Read the adapters
                context = await this.RemoteProvider.EndSessionAsync(context);

                context = await this.LocalProvider.EndSessionAsync(context);
            }
            catch (OperationCanceledException oce)
            {
                var error = SyncException.CreateOperationCanceledException(context.SyncStage, oce);
                HandleSyncError(error);
                hasErrors = true;
                throw;
            }
            catch (SyncException sex)
            {
                HandleSyncError(sex);
                hasErrors = true;
                throw;
            }
            catch (Exception ex)
            {
                var error = SyncException.CreateUnknowException(context.SyncStage, ex);
                HandleSyncError(error);
                hasErrors = true;
                throw;
            }
            finally
            {
                try
                {
                    if (hasErrors)
                    {
                        // if EndSessionAsync() was never called, try a last time
                        context = await this.RemoteProvider.EndSessionAsync(context);

                        context = await this.LocalProvider.EndSessionAsync(context);
                    }
                }
                catch (Exception)
                {
                    // no raise
                }

                this.SessionState = SyncSessionState.Ready;
                this.SessionStateChanged?.Invoke(this, this.SessionState);
            }

            return(context);
        }
コード例 #24
0
ファイル: CoreProvider.cs プロジェクト: aliusman/Dotmim.Sync
        /// <summary>
        /// Try to raise a specific progress event
        /// </summary>
        private void TryRaiseProgressEvent <T>(T args, EventHandler <T> handler) where T : BaseProgressEventArgs
        {
            args.Action = ChangeApplicationAction.Continue;

            handler?.Invoke(this, args);

            if (args.Action == ChangeApplicationAction.Rollback)
            {
                throw SyncException.CreateRollbackException(args.Stage);
            }

            var props = new Dictionary <String, String>();

            switch (args.Stage)
            {
            case SyncStage.None:
                break;

            case SyncStage.BeginSession:
                this.TryRaiseProgressEvent(SyncStage.BeginSession, $"Begin session");
                break;

            case SyncStage.ScopeLoading:
                props.Add("ScopeId", (args as ScopeEventArgs).ScopeInfo.Id.ToString());
                this.TryRaiseProgressEvent(SyncStage.ScopeLoading, $"Loading scope", props);
                break;

            case SyncStage.ScopeSaved:
                props.Add("ScopeId", (args as ScopeEventArgs).ScopeInfo.Id.ToString());
                this.TryRaiseProgressEvent(SyncStage.ScopeLoading, $"Scope saved", props);
                break;

            case SyncStage.ConfigurationApplying:
                this.TryRaiseProgressEvent(SyncStage.ConfigurationApplying, $"Applying configuration");
                break;

            case SyncStage.ConfigurationApplied:
                this.TryRaiseProgressEvent(SyncStage.ConfigurationApplied, $"Configuration applied");
                break;

            case SyncStage.DatabaseApplying:
                this.TryRaiseProgressEvent(SyncStage.DatabaseApplying, $"Applying database schemas");
                break;

            case SyncStage.DatabaseApplied:
                props.Add("Script", (args as DatabaseAppliedEventArgs).Script);
                this.TryRaiseProgressEvent(SyncStage.DatabaseApplied, $"Database schemas applied", props);
                break;

            case SyncStage.DatabaseTableApplying:
                props.Add("TableName", (args as DatabaseTableApplyingEventArgs).TableName);
                this.TryRaiseProgressEvent(SyncStage.DatabaseApplying, $"Applying schema table", props);
                break;

            case SyncStage.DatabaseTableApplied:
                props.Add("TableName", (args as DatabaseTableAppliedEventArgs).TableName);
                props.Add("Script", (args as DatabaseTableAppliedEventArgs).Script);
                this.TryRaiseProgressEvent(SyncStage.DatabaseApplied, $"Table schema applied", props);
                break;

            case SyncStage.TableChangesSelecting:
                props.Add("TableName", (args as TableChangesSelectingEventArgs).TableName);
                this.TryRaiseProgressEvent(SyncStage.TableChangesSelecting, $"Selecting changes", props);
                break;

            case SyncStage.TableChangesSelected:
                props.Add("TableName", (args as TableChangesSelectedEventArgs).TableChangesSelected.TableName);
                props.Add("Deletes", (args as TableChangesSelectedEventArgs).TableChangesSelected.Deletes.ToString());
                props.Add("Inserts", (args as TableChangesSelectedEventArgs).TableChangesSelected.Inserts.ToString());
                props.Add("Updates", (args as TableChangesSelectedEventArgs).TableChangesSelected.Updates.ToString());
                props.Add("TotalChanges", (args as TableChangesSelectedEventArgs).TableChangesSelected.TotalChanges.ToString());
                this.TryRaiseProgressEvent(SyncStage.TableChangesSelected, $"Changes selected", props);
                break;

            case SyncStage.TableChangesApplying:
                props.Add("TableName", (args as TableChangesApplyingEventArgs).TableName);
                props.Add("State", (args as TableChangesApplyingEventArgs).State.ToString());
                this.TryRaiseProgressEvent(SyncStage.TableChangesApplying, $"Applying changes", props);
                break;

            case SyncStage.TableChangesApplied:
                props.Add("TableName", (args as TableChangesAppliedEventArgs).TableChangesApplied.TableName);
                props.Add("State", (args as TableChangesAppliedEventArgs).TableChangesApplied.State.ToString());
                props.Add("Applied", (args as TableChangesAppliedEventArgs).TableChangesApplied.Applied.ToString());
                props.Add("Failed", (args as TableChangesAppliedEventArgs).TableChangesApplied.Failed.ToString());
                this.TryRaiseProgressEvent(SyncStage.TableChangesApplied, $"Changes applied", props);
                break;

            case SyncStage.EndSession:
                this.TryRaiseProgressEvent(SyncStage.EndSession, $"End session");
                break;

            case SyncStage.CleanupMetadata:
                break;
            }
        }