예제 #1
0
        private static SqlPreCommand SyncEnums(Schema schema, Table table, Dictionary <string, Entity> current, Dictionary <string, Entity> should)
        {
            var deletes = Synchronizer.SynchronizeScript(Spacing.Double, should, current,
                                                         createNew: null,
                                                         removeOld: (str, c) => table.DeleteSqlSync(c, comment: c.toStr),
                                                         mergeBoth: null);

            var moves = Synchronizer.SynchronizeScript(Spacing.Double, should, current,
                                                       createNew: null,
                                                       removeOld: null,
                                                       mergeBoth: (str, s, c) =>
            {
                if (s.id == c.id)
                {
                    return(table.UpdateSqlSync(c, comment: c.toStr));
                }

                var insert = table.InsertSqlSync(s);

                var move = (from t in schema.GetDatabaseTables()
                            from col in t.Columns.Values
                            where col.ReferenceTable == table
                            select new SqlPreCommandSimple("UPDATE {0} SET {1} = {2} WHERE {1} = {3} -- {4} re-indexed"
                                                           .FormatWith(t.Name, col.Name, s.Id, c.Id, c.toStr)))
                           .Combine(Spacing.Simple);

                var delete = table.DeleteSqlSync(c, comment: c.toStr);

                return(SqlPreCommand.Combine(Spacing.Simple, insert, move, delete));
            });

            var creates = Synchronizer.SynchronizeScript(Spacing.Double, should, current,
                                                         createNew: (str, s) => table.InsertSqlSync(s),
                                                         removeOld: null,
                                                         mergeBoth: null);

            return(SqlPreCommand.Combine(Spacing.Double, deletes, moves, creates));
        }
예제 #2
0
        public static SqlPreCommand SynchronizeTablesScript(Replacements replacements)
        {
            Dictionary <string, ITable> model        = Schema.Current.GetDatabaseTables().ToDictionaryEx(a => a.Name.ToString(), "schema tables");
            HashSet <SchemaName>        modelSchemas = Schema.Current.GetDatabaseTables().Select(a => a.Name.Schema).Where(a => !SqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet();

            Dictionary <string, DiffTable> database        = DefaultGetDatabaseDescription(Schema.Current.DatabaseNames());
            HashSet <SchemaName>           databaseSchemas = DefaultGetSchemas(Schema.Current.DatabaseNames());

            if (SimplifyDiffTables != null)
            {
                SimplifyDiffTables(database);
            }

            replacements.AskForReplacements(database.Keys.ToHashSet(), model.Keys.ToHashSet(), Replacements.KeyTables);

            database = replacements.ApplyReplacementsToOld(database, Replacements.KeyTables);

            Dictionary <ITable, Dictionary <string, Index> > modelIndices = model.Values
                                                                            .ToDictionary(t => t, t => t.GeneratAllIndexes().ToDictionaryEx(a => a.IndexName, "Indexes for {0}".FormatWith(t.Name)));

            model.JoinDictionaryForeach(database, (tn, tab, diff) =>
            {
                var key = Replacements.KeyColumnsForTable(tn);

                replacements.AskForReplacements(diff.Columns.Keys.ToHashSet(), tab.Columns.Keys.ToHashSet(), key);

                diff.Columns = replacements.ApplyReplacementsToOld(diff.Columns, key);

                diff.Indices = ApplyIndexAutoReplacements(diff, tab, modelIndices[tab]);
            });

            Func <ObjectName, ObjectName> ChangeName = (ObjectName objectName) =>
            {
                string name = replacements.Apply(Replacements.KeyTables, objectName.ToString());

                return(model.TryGetC(name)?.Name ?? objectName);
            };


            Func <ObjectName, SqlPreCommand> DeleteAllForeignKey = tableName =>
            {
                var dropFks = (from t in database.Values
                               from c in t.Columns.Values
                               where c.ForeignKey != null && c.ForeignKey.TargetTable.Equals(tableName)
                               select SqlBuilder.AlterTableDropConstraint(t.Name, c.ForeignKey.Name)).Combine(Spacing.Simple);

                if (dropFks == null)
                {
                    return(null);
                }

                return(SqlPreCommand.Combine(Spacing.Simple, new SqlPreCommandSimple("---In order to remove the PK of " + tableName.Name), dropFks));
            };

            using (replacements.WithReplacedDatabaseName())
            {
                SqlPreCommand createSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas",
                                                                                      modelSchemas.ToDictionary(a => a.ToString()),
                                                                                      databaseSchemas.ToDictionary(a => a.ToString()),
                                                                                      (_, newSN) => SqlBuilder.CreateSchema(newSN),
                                                                                      null,
                                                                                      (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : SqlBuilder.CreateSchema(newSN),
                                                                                      Spacing.Double);

                //use database without replacements to just remove indexes
                SqlPreCommand dropStatistics =
                    Synchronizer.SynchronizeScript(model, database,
                                                   null,
                                                   (tn, dif) => SqlBuilder.DropStatistics(tn, dif.Stats),
                                                   (tn, tab, dif) =>
                {
                    var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet();

                    return(SqlBuilder.DropStatistics(tn, dif.Stats.Where(a => a.Columns.Any(removedColums.Contains)).ToList()));
                },
                                                   Spacing.Double);

                SqlPreCommand dropIndices =
                    Synchronizer.SynchronizeScript(model, database,
                                                   null,
                                                   (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => SqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple),
                                                   (tn, tab, dif) =>
                {
                    Dictionary <string, Index> modelIxs = modelIndices[tab];

                    var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet();

                    var changes = Synchronizer.SynchronizeScript(modelIxs, dif.Indices,
                                                                 null,
                                                                 (i, dix) => dix.Columns.Any(removedColums.Contains) || dix.IsControlledIndex ? SqlBuilder.DropIndex(dif.Name, dix) : null,
                                                                 (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlPreCommand.Combine(Spacing.Double, dix.IsPrimary ? DeleteAllForeignKey(dif.Name) : null, SqlBuilder.DropIndex(dif.Name, dix)) : null,
                                                                 Spacing.Simple);

                    return(changes);
                },
                                                   Spacing.Double);

                SqlPreCommand dropForeignKeys = Synchronizer.SynchronizeScript(
                    model,
                    database,
                    null,
                    (tn, dif) => dif.Columns.Values.Select(c => c.ForeignKey != null ? SqlBuilder.AlterTableDropConstraint(dif.Name, c.ForeignKey.Name) : null)
                    .Concat(dif.MultiForeignKeys.Select(fk => SqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name))).Combine(Spacing.Simple),
                    (tn, tab, dif) => SqlPreCommand.Combine(Spacing.Simple,
                                                            Synchronizer.SynchronizeScript(
                                                                tab.Columns,
                                                                dif.Columns,
                                                                null,
                                                                (cn, colDb) => colDb.ForeignKey != null ? SqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null,
                                                                (cn, colModel, colDb) => colDb.ForeignKey == null ? null :
                                                                colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) ?
                                                                SqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) :
                                                                null, Spacing.Simple),
                                                            dif.MultiForeignKeys.Select(fk => SqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)),
                    Spacing.Double);

                SqlPreCommand tables =
                    Synchronizer.SynchronizeScript(
                        model,
                        database,
                        (tn, tab) => SqlBuilder.CreateTableSql(tab),
                        (tn, dif) => SqlBuilder.DropTable(dif.Name),
                        (tn, tab, dif) =>
                        SqlPreCommand.Combine(Spacing.Simple,
                                              !object.Equals(dif.Name, tab.Name) ? SqlBuilder.RenameOrMove(dif, tab) : null,
                                              Synchronizer.SynchronizeScript(
                                                  tab.Columns,
                                                  dif.Columns,
                                                  (cn, tabCol) => SqlPreCommandSimple.Combine(Spacing.Simple,
                                                                                              tabCol.PrimaryKey && dif.PrimaryKeyName != null ? SqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null,
                                                                                              AlterTableAddColumnDefault(tab, tabCol, replacements)),
                                                  (cn, difCol) => SqlPreCommandSimple.Combine(Spacing.Simple,
                                                                                              difCol.Default != null ? SqlBuilder.DropDefaultConstraint(tab.Name, difCol.Name) : null,
                                                                                              SqlBuilder.AlterTableDropColumn(tab, cn)),
                                                  (cn, tabCol, difCol) => SqlPreCommand.Combine(Spacing.Simple,
                                                                                                difCol.Name == tabCol.Name ? null : SqlBuilder.RenameColumn(tab, difCol.Name, tabCol.Name),
                                                                                                difCol.ColumnEquals(tabCol, ignorePrimaryKey: true) ? null : SqlPreCommand.Combine(Spacing.Simple,
                                                                                                                                                                                   tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? SqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null,
                                                                                                                                                                                   SqlBuilder.AlterTableAlterColumn(tab, tabCol),
                                                                                                                                                                                   tabCol.SqlDbType == SqlDbType.NVarChar && difCol.SqlDbType == SqlDbType.NChar ? SqlBuilder.UpdateTrim(tab, tabCol) : null),
                                                                                                difCol.DefaultEquals(tabCol) ? null : SqlPreCommand.Combine(Spacing.Simple,
                                                                                                                                                            difCol.Default != null ? SqlBuilder.DropDefaultConstraint(tab.Name, tabCol.Name) : null,
                                                                                                                                                            tabCol.Default != null ? SqlBuilder.AddDefaultConstraint(tab.Name, tabCol.Name, tabCol.Default) : null),
                                                                                                UpdateByFkChange(tn, difCol, tabCol, ChangeName)),
                                                  Spacing.Simple)),
                        Spacing.Double);

                if (tables != null)
                {
                    tables.GoAfter = true;
                }

                var tableReplacements = replacements.TryGetC(Replacements.KeyTables);
                if (tableReplacements != null)
                {
                    replacements[Replacements.KeyTablesInverse] = tableReplacements.Inverse();
                }

                SqlPreCommand syncEnums;

                try
                {
                    syncEnums = SynchronizeEnumsScript(replacements);
                }
                catch (Exception e)
                {
                    syncEnums = new SqlPreCommandSimple("-- Exception synchronizing enums: " + e.Message);
                }

                SqlPreCommand addForeingKeys = Synchronizer.SynchronizeScript(
                    model,
                    database,
                    (tn, tab) => SqlBuilder.AlterTableForeignKeys(tab),
                    null,
                    (tn, tab, dif) => Synchronizer.SynchronizeScript(
                        tab.Columns,
                        dif.Columns,
                        (cn, colModel) => colModel.ReferenceTable == null || colModel.AvoidForeignKey ? null :
                        SqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable),
                        null,
                        (cn, colModel, coldb) =>
                {
                    if (colModel.ReferenceTable == null || colModel.AvoidForeignKey)
                    {
                        return(null);
                    }

                    if (coldb.ForeignKey == null || !colModel.ReferenceTable.Name.Equals(ChangeName(coldb.ForeignKey.TargetTable)))
                    {
                        return(SqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable));
                    }

                    var name = SqlBuilder.ForeignKeyName(tab.Name.Name, colModel.Name);
                    return(SqlPreCommand.Combine(Spacing.Simple,
                                                 name != coldb.ForeignKey.Name.Name ? SqlBuilder.RenameForeignKey(coldb.ForeignKey.Name, name) : null,
                                                 (coldb.ForeignKey.IsDisabled || coldb.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? SqlBuilder.EnableForeignKey(tab.Name, name) : null));
                },
                        Spacing.Simple),
                    Spacing.Double);

                bool?createMissingFreeIndexes = null;

                SqlPreCommand addIndices =
                    Synchronizer.SynchronizeScript(model, database,
                                                   (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryClusteredIndex)).Select(SqlBuilder.CreateIndex).Combine(Spacing.Simple),
                                                   null,
                                                   (tn, tab, dif) =>
                {
                    var columnReplacements = replacements.TryGetC(Replacements.KeyColumnsForTable(tn));

                    Func <IColumn, bool> isNew = c => !dif.Columns.ContainsKey(columnReplacements?.TryGetC(c.Name) ?? c.Name);

                    Dictionary <string, Index> modelIxs = modelIndices[tab];

                    var controlledIndexes = Synchronizer.SynchronizeScript(modelIxs, dif.Indices,
                                                                           (i, mix) => mix is UniqueIndex || mix.Columns.Any(isNew) || SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) ? SqlBuilder.CreateIndex(mix) : null,
                                                                           null,
                                                                           (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlBuilder.CreateIndex(mix) :
                                                                           mix.IndexName != dix.IndexName ? SqlBuilder.RenameIndex(tab, dix.IndexName, mix.IndexName) : null,
                                                                           Spacing.Simple);

                    return(SqlPreCommand.Combine(Spacing.Simple, controlledIndexes));
                }, Spacing.Double);

                SqlPreCommand dropSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas",
                                                                                    modelSchemas.ToDictionary(a => a.ToString()),
                                                                                    databaseSchemas.ToDictionary(a => a.ToString()),
                                                                                    null,
                                                                                    (_, oldSN) => DropSchema(oldSN) ? SqlBuilder.DropSchema(oldSN) : null,
                                                                                    (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : SqlBuilder.DropSchema(oldSN),
                                                                                    Spacing.Double);

                return(SqlPreCommand.Combine(Spacing.Triple, createSchemas, dropStatistics, dropIndices, dropForeignKeys, tables, syncEnums, addForeingKeys, addIndices, dropSchemas));
            }
        }
예제 #3
0
        public static SqlPreCommand SynchronizeTablesScript(Replacements replacements)
        {
            Schema s = Schema.Current;

            Dictionary <string, ITable> model        = s.GetDatabaseTables().Where(t => !s.IsExternalDatabase(t.Name.Schema.Database)).ToDictionaryEx(a => a.Name.ToString(), "schema tables");
            HashSet <SchemaName>        modelSchemas = model.Values.Select(a => a.Name.Schema).Where(a => !SqlBuilder.SystemSchemas.Contains(a.Name)).ToHashSet();

            Dictionary <string, DiffTable> database        = DefaultGetDatabaseDescription(s.DatabaseNames());
            HashSet <SchemaName>           databaseSchemas = DefaultGetSchemas(s.DatabaseNames());

            SimplifyDiffTables?.Invoke(database);

            replacements.AskForReplacements(database.Keys.ToHashSet(), model.Keys.ToHashSet(), Replacements.KeyTables);

            database = replacements.ApplyReplacementsToOld(database, Replacements.KeyTables);

            Dictionary <ITable, Dictionary <string, Index> > modelIndices = model.Values
                                                                            .ToDictionary(t => t, t => t.GeneratAllIndexes().ToDictionaryEx(a => a.IndexName, "Indexes for {0}".FormatWith(t.Name)));

            //To --> From
            Dictionary <ObjectName, ObjectName> copyDataFrom = new Dictionary <ObjectName, ObjectName>();

            //A -> A_temp
            Dictionary <ObjectName, ObjectName> preRenames = new Dictionary <ObjectName, ObjectName>();

            model.JoinDictionaryForeach(database, (tn, tab, diff) =>
            {
                var key = Replacements.KeyColumnsForTable(tn);

                replacements.AskForReplacements(diff.Columns.Keys.ToHashSet(), tab.Columns.Keys.ToHashSet(), key);

                diff.Columns = replacements.ApplyReplacementsToOld(diff.Columns, key);

                diff.Indices = ApplyIndexAutoReplacements(diff, tab, modelIndices[tab]);

                var diffPk = diff.Columns.TryGetC(tab.PrimaryKey.Name);
                if (diffPk != null && tab.PrimaryKey.Identity != diffPk.Identity)
                {
                    if (tab.Name.Equals(diff.Name))
                    {
                        var tempName = new ObjectName(diff.Name.Schema, diff.Name.Name + "_old");
                        preRenames.Add(diff.Name, tempName);
                        copyDataFrom.Add(tab.Name, tempName);

                        if (replacements.Interactive)
                        {
                            SafeConsole.WriteLineColor(ConsoleColor.Yellow, $@"Column {diffPk.Name} in {diff.Name} is now Identity={tab.PrimaryKey.Identity}.");
                            Console.WriteLine($@"Changing a Primary Key is not supported by SQL Server so the script will...:
  1. Rename {diff.Name} table to {tempName}
  2. Create a new table {diff.Name} 
  3. Copy data from {tempName} to {tab.Name}.
  4. Drop {tempName}
");
                        }
                    }
                    else
                    {
                        copyDataFrom.Add(tab.Name, diff.Name);
                        if (replacements.Interactive)
                        {
                            SafeConsole.WriteLineColor(ConsoleColor.Yellow, $@"Column {diffPk.Name} in {diff.Name} is now Identity={tab.PrimaryKey.Identity}.");
                            Console.WriteLine($@"Changing a Primary Key is not supported by SQL Server so the script will...:
  1. Create a new table {tab.Name} 
  2. Copy data from {diff.Name} to {tab.Name}.
  3. Drop {diff.Name}
");
                        }
                    }
                }
            });

            var columnsByFKTarget = database.Values.SelectMany(a => a.Columns.Values).Where(a => a.ForeignKey != null).GroupToDictionary(a => a.ForeignKey.TargetTable);

            foreach (var pr in preRenames)
            {
                var diff = database[pr.Key.ToString()];
                diff.Name = pr.Value;
                foreach (var col in columnsByFKTarget.TryGetC(pr.Key).EmptyIfNull())
                {
                    col.ForeignKey.TargetTable = pr.Value;
                }

                database.Add(pr.Value.ToString(), diff);
                database.Remove(pr.Key.ToString());
            }

            Func <ObjectName, ObjectName> ChangeName = (ObjectName objectName) =>
            {
                string name = replacements.Apply(Replacements.KeyTables, objectName.ToString());

                return(model.TryGetC(name)?.Name ?? objectName);
            };


            Func <ObjectName, SqlPreCommand> DeleteAllForeignKey = tableName =>
            {
                var dropFks = (from t in database.Values
                               from c in t.Columns.Values
                               where c.ForeignKey != null && c.ForeignKey.TargetTable.Equals(tableName)
                               select SqlBuilder.AlterTableDropConstraint(t.Name, c.ForeignKey.Name)).Combine(Spacing.Simple);

                if (dropFks == null)
                {
                    return(null);
                }

                return(SqlPreCommand.Combine(Spacing.Simple, new SqlPreCommandSimple("---In order to remove the PK of " + tableName.Name), dropFks));
            };

            using (replacements.WithReplacedDatabaseName())
            {
                SqlPreCommand preRenameTables = preRenames.Select(a => SqlBuilder.RenameTable(a.Key, a.Value.Name)).Combine(Spacing.Double);

                if (preRenameTables != null)
                {
                    preRenameTables.GoAfter = true;
                }

                SqlPreCommand createSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas",
                                                                                      modelSchemas.ToDictionary(a => a.ToString()),
                                                                                      databaseSchemas.ToDictionary(a => a.ToString()),
                                                                                      (_, newSN) => SqlBuilder.CreateSchema(newSN),
                                                                                      null,
                                                                                      (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : SqlBuilder.CreateSchema(newSN),
                                                                                      Spacing.Double);

                //use database without replacements to just remove indexes
                SqlPreCommand dropStatistics =
                    Synchronizer.SynchronizeScript(model, database,
                                                   null,
                                                   (tn, dif) => SqlBuilder.DropStatistics(tn, dif.Stats),
                                                   (tn, tab, dif) =>
                {
                    var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet();

                    return(SqlBuilder.DropStatistics(tn, dif.Stats.Where(a => a.Columns.Any(removedColums.Contains)).ToList()));
                },
                                                   Spacing.Double);

                SqlPreCommand dropIndices =
                    Synchronizer.SynchronizeScript(model, database,
                                                   null,
                                                   (tn, dif) => dif.Indices.Values.Where(ix => !ix.IsPrimary).Select(ix => SqlBuilder.DropIndex(dif.Name, ix)).Combine(Spacing.Simple),
                                                   (tn, tab, dif) =>
                {
                    Dictionary <string, Index> modelIxs = modelIndices[tab];

                    var removedColums = dif.Columns.Keys.Except(tab.Columns.Keys).ToHashSet();

                    var changes = Synchronizer.SynchronizeScript(modelIxs, dif.Indices,
                                                                 null,
                                                                 (i, dix) => dix.Columns.Any(removedColums.Contains) || dix.IsControlledIndex ? SqlBuilder.DropIndex(dif.Name, dix) : null,
                                                                 (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlPreCommand.Combine(Spacing.Double, dix.IsPrimary ? DeleteAllForeignKey(dif.Name) : null, SqlBuilder.DropIndex(dif.Name, dix)) : null,
                                                                 Spacing.Simple);

                    return(changes);
                },
                                                   Spacing.Double);

                SqlPreCommand dropForeignKeys = Synchronizer.SynchronizeScript(
                    model,
                    database,
                    null,
                    (tn, dif) => dif.Columns.Values.Select(c => c.ForeignKey != null ? SqlBuilder.AlterTableDropConstraint(dif.Name, c.ForeignKey.Name) : null)
                    .Concat(dif.MultiForeignKeys.Select(fk => SqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name))).Combine(Spacing.Simple),
                    (tn, tab, dif) => SqlPreCommand.Combine(Spacing.Simple,
                                                            Synchronizer.SynchronizeScript(
                                                                tab.Columns,
                                                                dif.Columns,
                                                                null,
                                                                (cn, colDb) => colDb.ForeignKey != null ? SqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) : null,
                                                                (cn, colModel, colDb) => colDb.ForeignKey == null ? null :
                                                                colModel.ReferenceTable == null || colModel.AvoidForeignKey || !colModel.ReferenceTable.Name.Equals(ChangeName(colDb.ForeignKey.TargetTable)) ?
                                                                SqlBuilder.AlterTableDropConstraint(dif.Name, colDb.ForeignKey.Name) :
                                                                null, Spacing.Simple),
                                                            dif.MultiForeignKeys.Select(fk => SqlBuilder.AlterTableDropConstraint(dif.Name, fk.Name)).Combine(Spacing.Simple)),
                    Spacing.Double);

                SqlPreCommand preRenamePks = preRenames.Select(a => SqlBuilder.DropPrimaryKeyConstraint(a.Value)).Combine(Spacing.Double);

                SqlPreCommand tables =
                    Synchronizer.SynchronizeScript(
                        model,
                        database,
                        (tn, tab) => SqlPreCommand.Combine(Spacing.Double,
                                                           SqlBuilder.CreateTableSql(tab),
                                                           copyDataFrom.ContainsKey(tab.Name) ? CopyData(tab, database.GetOrThrow(copyDataFrom.GetOrThrow(tab.Name).ToString()), replacements).Do(a => a.GoBefore = true) : null
                                                           ),
                        (tn, dif) => SqlBuilder.DropTable(dif.Name),
                        (tn, tab, dif) =>
                        SqlPreCommand.Combine(Spacing.Simple,
                                              !object.Equals(dif.Name, tab.Name) ? SqlBuilder.RenameOrMove(dif, tab) : null,
                                              Synchronizer.SynchronizeScript(
                                                  tab.Columns,
                                                  dif.Columns,
                                                  (cn, tabCol) => SqlPreCommand.Combine(Spacing.Simple,
                                                                                        tabCol.PrimaryKey && dif.PrimaryKeyName != null ? SqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null,
                                                                                        AlterTableAddColumnDefault(tab, tabCol, replacements)),
                                                  (cn, difCol) => SqlPreCommand.Combine(Spacing.Simple,
                                                                                        difCol.Default != null ? SqlBuilder.DropDefaultConstraint(tab.Name, difCol.Name) : null,
                                                                                        SqlBuilder.AlterTableDropColumn(tab, cn)),
                                                  (cn, tabCol, difCol) => SqlPreCommand.Combine(Spacing.Simple,
                                                                                                difCol.Name == tabCol.Name ? null : SqlBuilder.RenameColumn(tab, difCol.Name, tabCol.Name),
                                                                                                difCol.ColumnEquals(tabCol, ignorePrimaryKey: true) ? null : SqlPreCommand.Combine(Spacing.Simple,
                                                                                                                                                                                   tabCol.PrimaryKey && !difCol.PrimaryKey && dif.PrimaryKeyName != null ? SqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null,
                                                                                                                                                                                   SqlBuilder.AlterTableAlterColumn(tab, tabCol),
                                                                                                                                                                                   tabCol.SqlDbType == SqlDbType.NVarChar && difCol.SqlDbType == SqlDbType.NChar ? SqlBuilder.UpdateTrim(tab, tabCol) : null),
                                                                                                difCol.DefaultEquals(tabCol) ? null : SqlPreCommand.Combine(Spacing.Simple,
                                                                                                                                                            difCol.Default != null ? SqlBuilder.DropDefaultConstraint(tab.Name, tabCol.Name) : null,
                                                                                                                                                            tabCol.Default != null ? SqlBuilder.AddDefaultConstraint(tab.Name, tabCol.Name, tabCol.Default, tabCol.SqlDbType) : null),
                                                                                                UpdateByFkChange(tn, difCol, tabCol, ChangeName, copyDataFrom)),
                                                  Spacing.Simple)),
                        Spacing.Double);

                if (tables != null)
                {
                    tables.GoAfter = true;
                }

                var tableReplacements = replacements.TryGetC(Replacements.KeyTables);
                if (tableReplacements != null)
                {
                    replacements[Replacements.KeyTablesInverse] = tableReplacements.Inverse();
                }

                SqlPreCommand syncEnums;

                try
                {
                    syncEnums = SynchronizeEnumsScript(replacements);
                }
                catch (Exception e)
                {
                    syncEnums = new SqlPreCommandSimple("-- Exception synchronizing enums: " + e.Message);
                }

                SqlPreCommand addForeingKeys = Synchronizer.SynchronizeScript(
                    model,
                    database,
                    (tn, tab) => SqlBuilder.AlterTableForeignKeys(tab),
                    null,
                    (tn, tab, dif) => Synchronizer.SynchronizeScript(
                        tab.Columns,
                        dif.Columns,
                        (cn, colModel) => colModel.ReferenceTable == null || colModel.AvoidForeignKey ? null :
                        SqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable),
                        null,
                        (cn, colModel, coldb) =>
                {
                    if (colModel.ReferenceTable == null || colModel.AvoidForeignKey)
                    {
                        return(null);
                    }

                    if (coldb.ForeignKey == null || !colModel.ReferenceTable.Name.Equals(ChangeName(coldb.ForeignKey.TargetTable)))
                    {
                        return(SqlBuilder.AlterTableAddConstraintForeignKey(tab, colModel.Name, colModel.ReferenceTable));
                    }

                    var name = SqlBuilder.ForeignKeyName(tab.Name.Name, colModel.Name);
                    return(SqlPreCommand.Combine(Spacing.Simple,
                                                 name != coldb.ForeignKey.Name.Name ? SqlBuilder.RenameForeignKey(coldb.ForeignKey.Name, name) : null,
                                                 (coldb.ForeignKey.IsDisabled || coldb.ForeignKey.IsNotTrusted) && !replacements.SchemaOnly ? SqlBuilder.EnableForeignKey(tab.Name, name) : null));
                },
                        Spacing.Simple),
                    Spacing.Double);

                bool?createMissingFreeIndexes = null;

                SqlPreCommand addIndices =
                    Synchronizer.SynchronizeScript(model, database,
                                                   (tn, tab) => modelIndices[tab].Values.Where(a => !(a is PrimaryClusteredIndex)).Select(SqlBuilder.CreateIndex).Combine(Spacing.Simple),
                                                   null,
                                                   (tn, tab, dif) =>
                {
                    var columnReplacements = replacements.TryGetC(Replacements.KeyColumnsForTable(tn));

                    Func <IColumn, bool> isNew = c => !dif.Columns.ContainsKey(columnReplacements?.TryGetC(c.Name) ?? c.Name);

                    Dictionary <string, Index> modelIxs = modelIndices[tab];

                    var controlledIndexes = Synchronizer.SynchronizeScript(modelIxs, dif.Indices,
                                                                           (i, mix) => mix is UniqueIndex || mix.Columns.Any(isNew) || SafeConsole.Ask(ref createMissingFreeIndexes, "Create missing non-unique index {0} in {1}?".FormatWith(mix.IndexName, tab.Name)) ? SqlBuilder.CreateIndex(mix) : null,
                                                                           null,
                                                                           (i, mix, dix) => !dix.IndexEquals(dif, mix) ? SqlBuilder.CreateIndex(mix) :
                                                                           mix.IndexName != dix.IndexName ? SqlBuilder.RenameIndex(tab, dix.IndexName, mix.IndexName) : null,
                                                                           Spacing.Simple);

                    return(SqlPreCommand.Combine(Spacing.Simple, controlledIndexes));
                }, Spacing.Double);

                SqlPreCommand dropSchemas = Synchronizer.SynchronizeScriptReplacing(replacements, "Schemas",
                                                                                    modelSchemas.ToDictionary(a => a.ToString()),
                                                                                    databaseSchemas.ToDictionary(a => a.ToString()),
                                                                                    null,
                                                                                    (_, oldSN) => DropSchema(oldSN) ? SqlBuilder.DropSchema(oldSN) : null,
                                                                                    (_, newSN, oldSN) => newSN.Equals(oldSN) ? null : SqlBuilder.DropSchema(oldSN),
                                                                                    Spacing.Double);

                return(SqlPreCommand.Combine(Spacing.Triple, preRenameTables, createSchemas, dropStatistics, dropIndices, dropForeignKeys, preRenamePks, tables, syncEnums, addForeingKeys, addIndices, dropSchemas));
            }
        }