/// <summary>
        /// Provision the orchestrator database based on the schema argument, and the provision enumeration
        /// </summary>
        /// <param name="schema">Schema to be applied to the database managed by the orchestrator, through the provider.</param>
        /// <param name="provision">Provision enumeration to determine which components to apply</param>
        /// <returns>Full schema with table and columns properties</returns>
        public virtual async Task <SyncSet> ProvisionAsync(SyncSet schema, SyncProvision provision, 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();

            using (var connection = this.Provider.CreateConnection())
            {
                // log
                this.logger.LogInformation(SyncEventsId.Provision, new { connection.Database, Provision = provision });

                try
                {
                    ctx.SyncStage = SyncStage.Provisioning;

                    // If schema does not have any table, just return
                    if (schema == null || schema.Tables == null || !schema.HasTables)
                    {
                        throw new MissingTablesException();
                    }

                    if (this is LocalOrchestrator && (provision.HasFlag(SyncProvision.ServerHistoryScope) || provision.HasFlag(SyncProvision.ServerScope)))
                    {
                        throw new InvalidProvisionForLocalOrchestratorException();
                    }
                    else if (!(this is LocalOrchestrator) && provision.HasFlag(SyncProvision.ClientScope))
                    {
                        throw new InvalidProvisionForRemoteOrchestratorException();
                    }


                    // 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);

                        // Check if we have tables AND columns
                        // If we don't have any columns it's most probably because user called method with the Setup only
                        // So far we have only tables names, it's enough to get the schema
                        if (schema.HasTables && !schema.HasColumns)
                        {
                            this.logger.LogInformation(SyncEventsId.GetSchema, this.Setup);
                            (ctx, schema) = await this.Provider.GetSchemaAsync(ctx, this.Setup, connection, transaction, cancellationToken, progress).ConfigureAwait(false);
                        }

                        if (!schema.HasTables)
                        {
                            throw new MissingTablesException();
                        }

                        if (!schema.HasColumns)
                        {
                            throw new MissingColumnsException();
                        }

                        await this.InterceptAsync(new DatabaseProvisioningArgs(ctx, provision, schema, connection, transaction), cancellationToken).ConfigureAwait(false);

                        await this.Provider.ProvisionAsync(ctx, schema, this.Setup, provision, this.Options.ScopeInfoTableName, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

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

                        transaction.Commit();
                    }

                    ctx.SyncStage = SyncStage.Provisioned;

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

                    var args = new DatabaseProvisionedArgs(ctx, provision, schema, connection);
                    await this.InterceptAsync(args, cancellationToken).ConfigureAwait(false);

                    this.ReportProgress(ctx, progress, args);
                }
                catch (Exception ex)
                {
                    RaiseError(ex);
                }
                finally
                {
                    if (connection != null && connection.State == ConnectionState.Open)
                    {
                        connection.Close();
                    }
                }

                return(schema);
            }
        }
Exemple #2
0
        /// <summary>
        /// Migrate an old setup configuration to a new one. This method is usefull if you are changing your SyncSetup when a database has been already configured previously
        /// </summary>
        public virtual async Task MigrationAsync(SyncSetup oldSetup, SyncSet schema, 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();

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

                    // If schema does not have any table, just return
                    if (schema == null || schema.Tables == null || !schema.HasTables)
                    {
                        throw new MissingTablesException();
                    }

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

                    SyncProvision provision = SyncProvision.None;

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

                        // Launch InterceptAsync on Migrating
                        await this.InterceptAsync(new DatabaseMigratingArgs(ctx, schema, oldSetup, this.Setup, connection, transaction), cancellationToken).ConfigureAwait(false);

                        this.logger.LogDebug(SyncEventsId.Migration, oldSetup);
                        this.logger.LogDebug(SyncEventsId.Migration, this.Setup);

                        // Migrate the db structure
                        await this.Provider.MigrationAsync(ctx, schema, oldSetup, this.Setup, true, connection, transaction, cancellationToken, progress);

                        // Now call the ProvisionAsync() to provision new tables
                        provision = SyncProvision.Table | SyncProvision.TrackingTable | SyncProvision.StoredProcedures | SyncProvision.Triggers;

                        await this.InterceptAsync(new DatabaseProvisioningArgs(ctx, provision, schema, connection, transaction), cancellationToken).ConfigureAwait(false);

                        // Provision new tables if needed
                        await this.Provider.ProvisionAsync(ctx, schema, this.Setup, provision, this.Options.ScopeInfoTableName, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

                        ScopeInfo localScope = null;

                        ctx = await this.Provider.EnsureClientScopeAsync(ctx, this.Options.ScopeInfoTableName, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

                        (ctx, localScope) = await this.Provider.GetClientScopeAsync(ctx, this.Options.ScopeInfoTableName, this.ScopeName, connection, transaction, cancellationToken, progress).ConfigureAwait(false);

                        localScope.Setup  = this.Setup;
                        localScope.Schema = schema;

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

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

                        transaction.Commit();
                    }

                    ctx.SyncStage = SyncStage.Migrated;

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

                    var args = new DatabaseProvisionedArgs(ctx, provision, schema, connection);
                    await this.InterceptAsync(args, cancellationToken).ConfigureAwait(false);

                    this.ReportProgress(ctx, progress, args);

                    // InterceptAsync Migrated
                    var args2 = new DatabaseMigratedArgs(ctx, schema, this.Setup);
                    await this.InterceptAsync(args2, cancellationToken).ConfigureAwait(false);

                    this.ReportProgress(ctx, progress, args2);
                }
                catch (Exception ex)
                {
                    RaiseError(ex);
                }
                finally
                {
                    if (connection != null && connection.State == ConnectionState.Open)
                    {
                        connection.Close();
                    }
                }
            }
        }
Exemple #3
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, MessageEnsureDatabase message)
        {
            DbConnection connection = null;

            try
            {
                // Event progress
                context.SyncStage = SyncStage.DatabaseApplying;

                var script = new StringBuilder();

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

                    using (var transaction = connection.BeginTransaction())
                    {
                        // Interceptor
                        var beforeArgs = new DatabaseProvisioningArgs(context, SyncProvision.All, message.Schema, connection, transaction);
                        await this.InterceptAsync(beforeArgs);

                        if (message.ScopeInfo.LastSync.HasValue && !beforeArgs.OverwriteSchema)
                        {
                            return(context);
                        }

                        // Sorting tables based on dependencies between them
                        var dmTables = message.Schema.Tables
                                       .SortByDependencies(tab => tab.ChildRelations
                                                           .Select(r => r.ChildTable));

                        foreach (var dmTable in dmTables)
                        {
                            var builder = this.GetDatabaseBuilder(dmTable);
                            // set if the builder supports creating the bulk operations proc stock
                            builder.UseBulkProcedures = this.SupportBulkOperations;

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

                            context.SyncStage = SyncStage.DatabaseTableApplying;

                            // Launch any interceptor if available
                            await this.InterceptAsync(new TableProvisioningArgs(context, SyncProvision.All, dmTable, connection, transaction));

                            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);

                            // Report & Interceptor
                            context.SyncStage = SyncStage.DatabaseTableApplied;
                            var tableProvisionedArgs = new TableProvisionedArgs(context, SyncProvision.All, dmTable, connection, transaction);
                            this.ReportProgress(context, tableProvisionedArgs);
                            await this.InterceptAsync(tableProvisionedArgs);
                        }

                        // Report & Interceptor
                        context.SyncStage = SyncStage.DatabaseApplied;
                        var args = new DatabaseProvisionedArgs(context, SyncProvision.All, message.Schema, script.ToString(), connection, transaction);
                        this.ReportProgress(context, args);
                        await this.InterceptAsync(args);

                        transaction.Commit();
                    }

                    connection.Close();

                    return(context);
                }
            }
            catch (Exception ex)
            {
                throw new SyncException(ex, SyncStage.DatabaseApplying);
            }
            finally
            {
                if (connection != null && connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }
            }
        }
Exemple #4
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, SyncSet schema,
                                                                    DbConnection connection, DbTransaction transaction,
                                                                    CancellationToken cancellationToken, IProgress <ProgressArgs> progress = null)
        {
            // Event progress
            context.SyncStage = SyncStage.SchemaApplying;

            var script = new StringBuilder();

            var beforeArgs = new DatabaseProvisioningArgs(context, SyncProvision.All, schema, connection, transaction);

            await this.InterceptAsync(beforeArgs).ConfigureAwait(false);

            // get Database builder
            var builder = this.GetDatabaseBuilder();

            builder.UseChangeTracking = this.UseChangeTracking;
            builder.UseBulkProcedures = this.SupportBulkOperations;

            // Initialize database if needed
            builder.EnsureDatabase(connection, transaction);

            // Sorting tables based on dependencies between them
            var schemaTables = schema.Tables
                               .SortByDependencies(tab => tab.GetRelations()
                                                   .Select(r => r.GetParentTable()));

            foreach (var schemaTable in schemaTables)
            {
                var tableBuilder = this.GetTableBuilder(schemaTable);
                // set if the builder supports creating the bulk operations proc stock
                tableBuilder.UseBulkProcedures = this.SupportBulkOperations;
                tableBuilder.UseChangeTracking = this.UseChangeTracking;

                // adding filter
                this.AddFilters(schemaTable, tableBuilder);

                context.SyncStage = SyncStage.TableSchemaApplying;

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


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

                // Report & Interceptor
                context.SyncStage = SyncStage.TableSchemaApplied;
                var tableProvisionedArgs = new TableProvisionedArgs(context, SyncProvision.All, schemaTable, connection, transaction);
                this.ReportProgress(context, progress, tableProvisionedArgs);
                await this.InterceptAsync(tableProvisionedArgs).ConfigureAwait(false);
            }

            // Report & Interceptor
            context.SyncStage = SyncStage.SchemaApplied;
            var args = new DatabaseProvisionedArgs(context, SyncProvision.All, schema, script.ToString(), connection, transaction);

            this.ReportProgress(context, progress, args);
            await this.InterceptAsync(args).ConfigureAwait(false);

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

            return(context);
        }