Exemple #1
0
        private void FindOneToOnes(IMap map, IAnswerProvider answerProvider) {
            foreach (var column in map.Columns.Where(c => c.Value.Relationship == RelationshipType.ManyToOne)) {
                var otherMapCandidates =
                    column.Value.ParentMap.Columns.Where(
                        c => c.Value.Type == column.Value.Map.Type && (column.Value.ParentMap != map || c.Key != column.Key)).ToArray();
                if (otherMapCandidates.Length == 0) {
                    continue;
                }
                else if (otherMapCandidates.Length == 1) {
                    column.Value.Relationship = RelationshipType.OneToOne; // one relationship coming back, assume one to one
                    column.Value.OppositeColumn = otherMapCandidates.First().Value;
                }
                else {
                    // we've got more than 1 foreign key coming back - let's ask the user
                    var choices = otherMapCandidates.Select(c => new MultipleChoice<IColumn> { DisplayString = c.Key, Choice = c.Value }).ToList();
                    const string oneToOneText = "No matching column but one-to-one";
                    const string manyToOneText = "No matching column but many-to-one";
                    choices.Add(new MultipleChoice<IColumn> { DisplayString = oneToOneText, Choice = new Column<string> { Name = "One to One" } });
                    choices.Add(new MultipleChoice<IColumn> { DisplayString = manyToOneText, Choice = new Column<string> { Name = "Many to One" } });
                    var oppositeColumn =
                        answerProvider.GetMultipleChoiceAnswer(
                            "The column " + column.Key + " on " + column.Value.Map.Table
                            + " has multiple incoming relationships. Which column on the related table is the other side of the one-to-one relationship?",
                            choices);
                    if (oppositeColumn.DisplayString == manyToOneText) {
                        continue; // many to one
                    }

                    column.Value.Relationship = RelationshipType.OneToOne;
                    if (oppositeColumn.DisplayString != oneToOneText) {
                        column.Value.OppositeColumn = oppositeColumn.Choice;
                    }
                }
            }
        }
Exemple #2
0
        public IEnumerable<IMap> ReverseEngineer(
            DatabaseSchema schema,
            ISqlDialect sqlDialect,
            IEnumerable<string> tablesToIgnore,
            IAnswerProvider answerProvider,
            bool fixOneToOnes) {
            if (tablesToIgnore == null) {
                tablesToIgnore = new string[0];
            }

            var maps = new List<IMap>();
            this.configuration = new Configuration(sqlDialect);
            foreach (var table in schema.Tables.Where(t => !tablesToIgnore.Contains(t.Name))) {
                maps.Add(this.MapTable(table));
            }

            // go back through and add indexes and foreign keys
            foreach (var map in maps) {
                GetIndexesAndForeignKeys(schema.Tables.First(t => t.Name == map.Table), map);
            }

            // go back through and try to spot one-to-one columns
            if (fixOneToOnes) {
                foreach (var map in maps) {
                    FindOneToOnes(map, answerProvider);
                }
            }

            return maps;
        }
 public AnswerController(
     ApplicationDbContext dbContext,
     IAnswerProvider DataProcessor,
     RoleManager <IdentityRole> roleManager,
     UserManager <ApplicationUser> userManager,
     IConfiguration configuration)
     : base(dbContext, roleManager, userManager, configuration)
 {
     dataProcessor = DataProcessor;
 }
Exemple #4
0
        private void FindOneToOnes(IMap map, IAnswerProvider answerProvider)
        {
            foreach (var column in map.Columns.Where(c => c.Value.Relationship == RelationshipType.ManyToOne))
            {
                var otherMapCandidates =
                    column.Value.ParentMap.Columns.Where(
                        c => c.Value.Type == column.Value.Map.Type && (column.Value.ParentMap != map || c.Key != column.Key)).ToArray();
                if (otherMapCandidates.Length == 0)
                {
                    continue;
                }
                else if (otherMapCandidates.Length == 1)
                {
                    column.Value.Relationship   = RelationshipType.OneToOne; // one relationship coming back, assume one to one
                    column.Value.OppositeColumn = otherMapCandidates.First().Value;
                }
                else
                {
                    // we've got more than 1 foreign key coming back - let's ask the user
                    var choices = otherMapCandidates.Select(c => new MultipleChoice <IColumn> {
                        DisplayString = c.Key, Choice = c.Value
                    }).ToList();
                    const string oneToOneText  = "No matching column but one-to-one";
                    const string manyToOneText = "No matching column but many-to-one";
                    choices.Add(new MultipleChoice <IColumn> {
                        DisplayString = oneToOneText, Choice = new Column <string> {
                            Name = "One to One"
                        }
                    });
                    choices.Add(new MultipleChoice <IColumn> {
                        DisplayString = manyToOneText, Choice = new Column <string> {
                            Name = "Many to One"
                        }
                    });
                    var oppositeColumn =
                        answerProvider.GetMultipleChoiceAnswer(
                            "The column " + column.Key + " on " + column.Value.Map.Table
                            + " has multiple incoming relationships. Which column on the related table is the other side of the one-to-one relationship?",
                            choices);
                    if (oppositeColumn.DisplayString == manyToOneText)
                    {
                        continue; // many to one
                    }

                    column.Value.Relationship = RelationshipType.OneToOne;
                    if (oppositeColumn.DisplayString != oneToOneText)
                    {
                        column.Value.OppositeColumn = oppositeColumn.Choice;
                    }
                }
            }
        }
Exemple #5
0
        public IDictionary<string, string> GenerateFiles(
            IEnumerable<IMap> maps,
            DatabaseSchema schema,
            string domainNamespace,
            IAnswerProvider answerProvider) {
            // note that we're just doing string building here
            // simple POCOs and CodeDom does not support auto-properties
            // and frankly I wouldn't want these things to have backing fields in the source code
            var result = new Dictionary<string, string>();

            // iterate over the configuration generating classes
            foreach (var map in maps) {
                this.GenerateClass(result, map, schema, domainNamespace);
            }

            return result;
        }
Exemple #6
0
        public IDictionary <string, string> GenerateFiles(
            IEnumerable <IMap> maps,
            Database schema,
            string domainNamespace,
            IAnswerProvider answerProvider)
        {
            // note that we're just doing string building here
            // simple POCOs and CodeDom does not support auto-properties
            // and frankly I wouldn't want these things to have backing fields in the source code
            var result = new Dictionary <string, string>();

            // iterate over the configuration generating classes
            foreach (var map in maps)
            {
                this.GenerateClass(result, map, schema, domainNamespace);
            }

            return(result);
        }
Exemple #7
0
        public IEnumerable <IMap> ReverseEngineer(
            DatabaseSchema schema,
            ISqlDialect sqlDialect,
            IEnumerable <string> tablesToIgnore,
            IAnswerProvider answerProvider,
            bool fixOneToOnes)
        {
            if (tablesToIgnore == null)
            {
                tablesToIgnore = new string[0];
            }

            var maps = new List <IMap>();

            this.configuration = new Configuration(sqlDialect);
            foreach (var table in schema.Tables.Where(t => !tablesToIgnore.Contains(t.Name)))
            {
                maps.Add(this.MapTable(table));
            }

            // go back through and add indexes and foreign keys
            foreach (var map in maps)
            {
                GetIndexesAndForeignKeys(schema.Tables.First(t => t.Name == map.Table), map);
            }

            // go back through and try to spot one-to-one columns
            if (fixOneToOnes)
            {
                foreach (var map in maps)
                {
                    FindOneToOnes(map, answerProvider);
                }
            }

            return(maps);
        }
Exemple #8
0
        public string GenerateSqlDiff(
            IEnumerable <IMap> fromMaps,
            IEnumerable <IMap> toMaps,
            IAnswerProvider answerProvider,
            ILogger logger,
            IEnumerable <string> indexesToIgnore,
            out IEnumerable <string> warnings,
            out IEnumerable <string> errors)
        {
            // fetch data for current database
            IDictionary <string, Statistics> currentData = new Dictionary <string, Statistics>();

            if (fromMaps.Any())
            {
                currentData = this.statisticsProvider.GetStatistics(fromMaps);
            }

            var sql         = new StringBuilder();
            var warningList = new List <string>();
            var errorList   = new List <string>();
            var renamePrimaryKeyModifications = new Dictionary <Tuple <string, string>, bool>();
            var from = fromMaps.ToArray();
            var to   = toMaps.ToArray();

            // get additions and removals
            var mapComparer = new TableNameEqualityComparer();
            var additions   = to.Except(from, mapComparer).ToList();
            var removals    = from.Except(to, mapComparer).ToList();
            var matches     = from.Join(to, f => f.Table, t => t.Table, MigrationPair.Of).ToList();

            // trace output
            logger.Trace("Additions:");
            logger.Trace("");
            logger.Trace(additions.Select(a => new { a.Table, a.Type.Name }), new[] { "Table", "Map Name" });
            logger.Trace("Removals:");
            logger.Trace("");
            logger.Trace(removals.Select(r => new { r.Table, r.Type.Name }), new[] { "Table", "Map Name" });
            logger.Trace("Matches:");
            logger.Trace("");
            logger.Trace(
                matches.Select(m => new { FromTable = m.From.Table, FromMap = m.From.Type.Name, ToTable = m.To.Table, ToMap = m.To.Type.Name }),
                new[] { "From Table", "From Map", "To Table", "To Map" });

            // look for possible entity name changes
            if (additions.Any() && removals.Any())
            {
                // TODO do something a bit more sensible with regards to likelihood of rename
                foreach (var removed in removals.Select(r => r).ToArray())
                {
                    // copy the array as we'll update
                    var answer =
                        answerProvider.GetMultipleChoiceAnswer(
                            string.Format("The entity {0} has been removed. If it has been renamed please specify what to:", removed.Type.Name),
                            new[] { new MultipleChoice <string> {
                                        DisplayString = "Not renamed - please delete", Choice = NoRename
                                    } }.Union(
                                additions.Select(a => new MultipleChoice <string> {
                        Choice = a.Type.Name, DisplayString = a.Type.Name
                    })));
                    if (answer.Choice != NoRename)
                    {
                        // rename the table
                        var renameFrom = removed;
                        var renameTo   = additions.First(a => a.Type.Name == answer.Choice);
                        sql.AppendSql(this.alterTableWriter.RenameTable(renameFrom, renameTo));

                        // add to the matches
                        matches.Add(MigrationPair.Of(renameFrom, renameTo));

                        // modify additions and removals
                        removals.Remove(renameFrom);
                        additions.Remove(renameTo);

                        // sort out the primary key
                        var fromPrimaryKey = renameFrom.PrimaryKey;
                        var toPrimaryKey   = renameTo.PrimaryKey;
                        if (!AreColumnDefinitionsEqual(fromPrimaryKey, toPrimaryKey))
                        {
                            if (fromPrimaryKey.DbName != toPrimaryKey.DbName && fromPrimaryKey.DbType == toPrimaryKey.DbType &&
                                (!fromPrimaryKey.DbType.TypeTakesLength() || (fromPrimaryKey.MaxLength && toPrimaryKey.MaxLength) ||
                                 (fromPrimaryKey.Length == toPrimaryKey.Length)) &&
                                (!fromPrimaryKey.DbType.TypeTakesPrecisionAndScale() ||
                                 (fromPrimaryKey.Precision == toPrimaryKey.Precision && fromPrimaryKey.Scale == toPrimaryKey.Scale)))
                            {
                                // just a change in name
                                sql.AppendSql(this.alterTableWriter.ChangeColumnName(fromPrimaryKey, toPrimaryKey));
                            }
                            else
                            {
                                // ask the question
                                // TODO may things more sensible based on the type
                                var attemptChange =
                                    answerProvider.GetBooleanAnswer(
                                        "The primary key change required for this table rename involves a change "
                                        + (fromPrimaryKey.DbType != toPrimaryKey.DbType ? "of data type" : "of specification")
                                        + ". Would you like to attempt the change? (Selecting No will drop and re-create the column)");
                                if (attemptChange)
                                {
                                    if (fromPrimaryKey.DbName != toPrimaryKey.DbName)
                                    {
                                        sql.AppendSql(this.alterTableWriter.ChangeColumnName(fromPrimaryKey, toPrimaryKey));
                                    }

                                    sql.AppendSql(this.alterTableWriter.ModifyColumn(fromPrimaryKey, toPrimaryKey));
                                    renamePrimaryKeyModifications.Add(Tuple.Create(fromPrimaryKey.Map.Type.Name, toPrimaryKey.Map.Type.Name), true);
                                }
                                else
                                {
                                    // drop and re-create
                                    sql.AppendSql(this.alterTableWriter.DropColumn(fromPrimaryKey));
                                    sql.AppendSql(this.alterTableWriter.AddColumn(toPrimaryKey));
                                    renamePrimaryKeyModifications.Add(Tuple.Create(fromPrimaryKey.Map.Type.Name, toPrimaryKey.Map.Type.Name), false);
                                }
                            }
                        }
                    }
                }
            }

            // do removal of foreign keys and indexes that we don't need
            // only do this on remaining tables as drops should be deleted automatically
            foreach (var matchPair in matches)
            {
                var fkRemovals = matchPair.From.ForeignKeys.Except(matchPair.To.ForeignKeys);
                foreach (var foreignKey in fkRemovals)
                {
                    sql.AppendSql(this.alterTableWriter.DropForeignKey(foreignKey));
                }

                var indexRemovals = matchPair.From.Indexes.Except(matchPair.To.Indexes);
                foreach (var index in indexRemovals.Where(i => !indexesToIgnore.Contains(i.Name)))
                {
                    sql.AppendSql(this.alterTableWriter.DropIndex(index));
                }
            }

            // do renames of columns
            var columnKeyValuePairEqualityComparer = new ColumnKeyValuePairEqualityComparer();
            var addedProperties = new List <IColumn>();

            foreach (var pair in matches)
            {
                var fromCols = pair.From.OwnedColumns(true).ToDictionary(c => c.DbName, c => c);
                var toCols   = pair.To.OwnedColumns(true).ToDictionary(c => c.DbName, c => c);

                var removedColumns = fromCols.Except(toCols, columnKeyValuePairEqualityComparer);
                var addedColumns   = toCols.Except(fromCols, columnKeyValuePairEqualityComparer).ToList();
                if (removedColumns.Any())
                {
                    // handle drops and renames
                    foreach (var removal in removedColumns)
                    {
                        if (pair.From.Type.Name != pair.To.Type.Name && removal.Value.IsPrimaryKey)
                        {
                            // ignore this one and get the new pk and remove it as handled above
                            var newPrimaryKey = toCols.Single(c => c.Value.IsPrimaryKey);
                            addedColumns.Remove(newPrimaryKey);
                            continue;
                        }

                        if (addedColumns.Any())
                        {
                            var answer =
                                answerProvider.GetMultipleChoiceAnswer(
                                    string.Format(
                                        "The property {0} has been removed. If it has been renamed please specify what to:",
                                        removal.Value.Name),
                                    new[] { new MultipleChoice <string> {
                                                DisplayString = "Not renamed - please delete", Choice = NoRename
                                            } }.Union(
                                        addedColumns.Select(a => new MultipleChoice <string> {
                                Choice = a.Value.Name, DisplayString = a.Value.Name
                            })));
                            if (answer.Choice == NoRename)
                            {
                                // drop the column
                                if (pair.From.Type.Name != pair.To.Type.Name)
                                {
                                    removal.Value.Map = pair.To; // want to delete from the correctly named table in the event of a rename
                                }

                                sql.AppendSql(this.alterTableWriter.DropColumn(removal.Value));
                            }
                            else
                            {
                                // rename the column
                                var toColumn = addedColumns.First(c => c.Value.Name == answer.Choice);
                                sql.AppendSql(this.alterTableWriter.ChangeColumnName(removal.Value, toColumn.Value));

                                // if need be perform a modify statement
                                if (this.RequiresColumnSpecificationChange(removal.Value, toColumn.Value))
                                {
                                    sql.AppendSql(this.alterTableWriter.ModifyColumn(removal.Value, toColumn.Value));
                                }

                                // remove the column from the additions
                                addedColumns.Remove(toColumn);
                            }
                        }
                        else
                        {
                            // drop the column
                            if (pair.From.Type.Name != pair.To.Type.Name)
                            {
                                removal.Value.Map = pair.To; // want to delete from the correctly named table in the event of a rename
                            }

                            sql.AppendSql(this.alterTableWriter.DropColumn(removal.Value));
                        }
                    }
                }

                // go through existing columns and handle modifications
                foreach (var fromProp in pair.From.Columns)
                {
                    logger.Trace("Examining {1}.{0}", fromProp.Value.Name, pair.From.Table);
                    var matchingToProp = pair.To.Columns.Select(p => p.Value).FirstOrDefault(p => p.Name == fromProp.Key);
                    if (matchingToProp != null)
                    {
                        if (this.RequiresColumnSpecificationChange(fromProp.Value, matchingToProp))
                        {
                            // check for potential errors
                            if (fromProp.Value.DbType != matchingToProp.DbType)
                            {
                                bool skipQuestion = false;
                                bool wasPrimaryKeyDroppedAndRecreated = false;
                                var  renamePrimaryKeyModificationsKey =
                                    Tuple.Create(
                                        fromProp.Value.Relationship == RelationshipType.ManyToOne
                                            ? fromProp.Value.ParentMap.Type.Name
                                            : fromProp.Value.OppositeColumn.ParentMap.Type.Name,
                                        matchingToProp.Relationship == RelationshipType.ManyToOne
                                            ? matchingToProp.ParentMap.Type.Name
                                            : matchingToProp.OppositeColumn.Map.Type.Name);
                                if ((fromProp.Value.Relationship == RelationshipType.OneToOne ||
                                     fromProp.Value.Relationship == RelationshipType.ManyToOne) &&
                                    (matchingToProp.Relationship == RelationshipType.ManyToOne ||
                                     matchingToProp.Relationship == RelationshipType.OneToOne) &&
                                    renamePrimaryKeyModifications.ContainsKey(renamePrimaryKeyModificationsKey))
                                {
                                    // skip the question as we've already attempted the modify for the pk so may as well here as well!
                                    skipQuestion = true;
                                    wasPrimaryKeyDroppedAndRecreated = !renamePrimaryKeyModifications[renamePrimaryKeyModificationsKey];
                                }

                                bool dropAndRecreate = wasPrimaryKeyDroppedAndRecreated;
                                if (!skipQuestion)
                                {
                                    dropAndRecreate =
                                        answerProvider.GetBooleanAnswer(
                                            string.Format(
                                                "Attempting to change DbType for property {0} on {1} from {2} to {3}. Would you like to attempt the change? (selecting \"No\" will drop and re-create the column)",
                                                matchingToProp.Name,
                                                matchingToProp.Map.Type.Name,
                                                fromProp.Value.DbType,
                                                matchingToProp.DbType));
                                }

                                if (dropAndRecreate)
                                {
                                    sql.AppendSql(this.alterTableWriter.DropColumn(matchingToProp));
                                    sql.AppendSql(this.alterTableWriter.AddColumn(matchingToProp));
                                    continue;
                                }

                                warningList.Add(
                                    string.Format(
                                        "Changing DB Type is not guaranteed to work: {0} on {1}",
                                        fromProp.Value.Name,
                                        fromProp.Value.Map.Type.Name));
                            }

                            if ((this.RequiresLengthChange(fromProp.Value, matchingToProp) &&
                                 (fromProp.Value.MaxLength || fromProp.Value.Length < matchingToProp.Length)) ||
                                (this.RequiresPrecisionOrScaleChange(fromProp.Value, matchingToProp) &&
                                 (fromProp.Value.Precision > matchingToProp.Precision || fromProp.Value.Scale > matchingToProp.Scale)))
                            {
                                warningList.Add(
                                    string.Format(
                                        "{0} on {1} is having its precision, scale or length reduced. This may result in loss of data",
                                        fromProp.Value.Name,
                                        fromProp.Value.Map.Type.Name));
                            }

                            sql.AppendSql(this.alterTableWriter.ModifyColumn(fromProp.Value, matchingToProp));
                        }
                        else
                        {
                            if ((matchingToProp.Relationship == RelationshipType.ManyToOne || matchingToProp.Relationship == RelationshipType.OneToOne) &&
                                (fromProp.Value.Relationship == RelationshipType.ManyToOne ||
                                 fromProp.Value.Relationship == RelationshipType.OneToOne) &&
                                fromProp.Value.Type.Name != matchingToProp.Type.Name && currentData != null &&
                                currentData[matchingToProp.Map.Type.Name].HasRows)
                            {
                                warningList.Add(
                                    string.Format(
                                        "Property {0} on {1} has changed type but the column was not dropped. There is data in that table, please empty that column if necessary",
                                        matchingToProp.Name,
                                        matchingToProp.Map.Type.Name));
                            }
                        }
                    }
                }

                // add the added columns to the addedProperties list
                addedProperties.AddRange(addedColumns.Select(c => c.Value));
            }

            // do deletes of entities
            foreach (var removal in removals)
            {
                sql.AppendSql(this.dropTableWriter.DropTable(removal));
            }

            // do additions of entities
            foreach (var addition in additions)
            {
                sql.AppendSql(this.createTableWriter.CreateTable(addition));
            }

            // do additions of properties
            foreach (var newProp in addedProperties)
            {
                // check for relationships where the related table is not empty and the prop is not null
                if ((newProp.Relationship == RelationshipType.ManyToOne || newProp.Relationship == RelationshipType.OneToOne) && !newProp.IsNullable &&
                    string.IsNullOrWhiteSpace(newProp.Default) && currentData.ContainsKey(newProp.Map.Type.Name) &&
                    currentData[newProp.Map.Type.Name].HasRows)
                {
                    var foreignKeyPrimaryKeyType = newProp.Relationship == RelationshipType.ManyToOne
                                                       ? newProp.ParentMap.PrimaryKey.Type
                                                       : newProp.OppositeColumn.Map.PrimaryKey.Type;
                    var answer = answerProvider.GetType()
                                 .GetMethod("GetAnswer")
                                 .MakeGenericMethod(foreignKeyPrimaryKeyType)
                                 .Invoke(
                        answerProvider,
                        new object[] {
                        string.Format(
                            "You are adding a property {0} on {1} that has a foreign key to {2} (which is non-empty) with primary key type {3}. Please specify a default value for the column:",
                            newProp.Name,
                            newProp.Map.Type.Name,
                            newProp.Type.Name,
                            foreignKeyPrimaryKeyType)
                    });
                    newProp.Default = answer.ToString();
                }

                sql.AppendSql(this.alterTableWriter.AddColumn(newProp));
            }

            // add in new foreign keys for additions
            foreach (var map in additions)
            {
                var statements = this.createTableWriter.CreateForeignKeys(map);
                foreach (var statement in statements)
                {
                    sql.AppendSql(statement);
                }
            }

            // add in new indexes for additions
            foreach (var map in additions)
            {
                var statements = this.createTableWriter.CreateIndexes(map);
                foreach (var statement in statements)
                {
                    sql.AppendSql(statement);
                }
            }

            // add in missing foreign keys and indexes
            foreach (var matchPair in matches)
            {
                var fkAdditions  = matchPair.To.ForeignKeys.Except(matchPair.From.ForeignKeys);
                var fkStatements = this.createTableWriter.CreateForeignKeys(fkAdditions);
                foreach (var statement in fkStatements)
                {
                    sql.AppendSql(statement);
                }

                var indexAdditions  = matchPair.To.Indexes.Except(matchPair.From.Indexes);
                var indexStatements = this.createTableWriter.CreateIndexes(indexAdditions);
                foreach (var statement in indexStatements)
                {
                    sql.AppendSql(statement);
                }
            }

            warnings = warningList;
            errors   = errorList;
            return(sql.ToString());
        }
Exemple #9
0
        private static void InnerMain(string[] args)
        {
            var options = new CommandLineOptions();

            if (!Parser.Default.ParseArguments(args, options))
            {
                ShowHelpText(options);
                return;
            }

            isVerbose = options.Verbose;

            // weaving
            if (options.Weave)
            {
                if (string.IsNullOrWhiteSpace(options.WeaveDir))
                {
                    throw new CatchyException("You must specify the directory to weave");
                }

                if (!options.IgnorePeVerify)
                {
                    TryFindIgnoreConfigSetting(options);
                }

                var task = new ExtendDomainTask {
                    LaunchDebugger = options.LaunchDebugger,
                    WeaveDir       = options.WeaveDir,
                    Logger         = new ConsoleLogger(options.Verbose),
                    IgnorePEVerify = options.IgnorePeVerify
                };
                if (!task.Execute())
                {
                    throw new CatchyException("Weaving failed");
                }

                return;
            }

            // prevalidation
            if (string.IsNullOrWhiteSpace(options.ConfigPath))
            {
                throw new CatchyException("You must specify a configuration path or a project name");
            }

            if (!File.Exists(options.ConfigPath))
            {
                throw new CatchyException("Could not locate configuration file {0}", options.ConfigPath);
            }

            // dependency init
            consoleAnswerProvider = new ConsoleAnswerProvider("~" + Path.GetFileNameWithoutExtension(options.ConfigPath) + ".answers");

            // parse all of the configuration stuffs
            ConnectionStringSettings connectionStringSettings;
            DashingSettings          dashingSettings;
            ReverseEngineerSettings  reverseEngineerSettings;

            ParseIni(options, out connectionStringSettings, out dashingSettings, out reverseEngineerSettings);

            // postvalidation
            if (!File.Exists(dashingSettings.PathToDll))
            {
                throw new CatchyException("Could not locate {0}", dashingSettings.PathToDll);
            }

            // load the configuration NOW and try to inherit its version of Dashing, Dapper, etc
            var configAssembly = Assembly.LoadFrom(dashingSettings.PathToDll);

            GC.KeepAlive(configAssembly);
            configObject = LoadConfiguration(configAssembly, dashingSettings, connectionStringSettings);

            // now decide what to do
            if (options.Script)
            {
                DoScript(options.Location, options.Naive, connectionStringSettings, dashingSettings, reverseEngineerSettings);
            }
            else if (options.Seed)
            {
                DoSeed(connectionStringSettings);
            }
            else if (options.Migration)
            {
                DoMigrate(options.Naive, connectionStringSettings, dashingSettings, reverseEngineerSettings);
            }
            else if (options.ReverseEngineer)
            {
                DoReverseEngineer(options, dashingSettings, reverseEngineerSettings, connectionStringSettings);
            }
            else
            {
                ShowHelpText(options);
            }
        }
Exemple #10
0
 public AnswerController(IAnswerProvider answerProvider)
 {
     _answerProvider = answerProvider;
 }
Exemple #11
0
        private static void InnerMain(string[] args) {
            var options = new CommandLineOptions();

            if (!Parser.Default.ParseArguments(args, options)) {
                ShowHelpText(options);
                return;
            }

            isVerbose = options.Verbose;

            // weaving
            if (options.Weave) {
                if (string.IsNullOrWhiteSpace(options.WeaveDir)) {
                    throw new CatchyException("You must specify the directory to weave");
                }

                if (!options.IgnorePeVerify) {
                    TryFindIgnoreConfigSetting(options);
                }

                var task = new ExtendDomainTask {
                    LaunchDebugger = options.LaunchDebugger,
                    WeaveDir = options.WeaveDir,
                    Logger = new ConsoleLogger(options.Verbose),
                    IgnorePEVerify = options.IgnorePeVerify
                };
                if (!task.Execute()) {
                    throw new CatchyException("Weaving failed");
                }

                return;
            }

            // prevalidation
            if (string.IsNullOrWhiteSpace(options.ConfigPath)) {
                throw new CatchyException("You must specify a configuration path or a project name");
            }

            if (!File.Exists(options.ConfigPath)) {
                throw new CatchyException("Could not locate configuration file {0}", options.ConfigPath);
            }

            // dependency init
            consoleAnswerProvider = new ConsoleAnswerProvider("~" + Path.GetFileNameWithoutExtension(options.ConfigPath) + ".answers");

            // parse all of the configuration stuffs
            ConnectionStringSettings connectionStringSettings;
            DashingSettings dashingSettings;
            ReverseEngineerSettings reverseEngineerSettings;
            ParseIni(options, out connectionStringSettings, out dashingSettings, out reverseEngineerSettings);

            // postvalidation
            if (!File.Exists(dashingSettings.PathToDll)) {
                throw new CatchyException("Could not locate {0}", dashingSettings.PathToDll);
            }

            // load the configuration NOW and try to inherit its version of Dashing, Dapper, etc
            var configAssembly = Assembly.LoadFrom(dashingSettings.PathToDll);
            GC.KeepAlive(configAssembly);
            configObject = LoadConfiguration(configAssembly, dashingSettings, connectionStringSettings);

            // now decide what to do
            if (options.Script) {
                DoScript(options.Location, options.Naive, connectionStringSettings, dashingSettings, reverseEngineerSettings);
            }
            else if (options.Seed) {
                DoSeed(connectionStringSettings);
            }
            else if (options.Migration) {
                DoMigrate(options.Naive, connectionStringSettings, dashingSettings, reverseEngineerSettings);
            }
            else if (options.ReverseEngineer) {
                DoReverseEngineer(options, dashingSettings, reverseEngineerSettings, connectionStringSettings);
            }
            else {
                ShowHelpText(options);
            }
        }
Exemple #12
0
        public void Execute(IConfiguration configuration, string connectionString, string providerName, IEnumerable <string> tablesToIgnore, IEnumerable <string> indexesToIgnore, IEnumerable <KeyValuePair <string, string> > extraPluralizationWords, IAnswerProvider answerProvider)
        {
            var script = this.scriptGenerator.Generate(configuration, connectionString, providerName, tablesToIgnore, indexesToIgnore, extraPluralizationWords, answerProvider);

            if (string.IsNullOrWhiteSpace(script))
            {
                using (new ColorContext(ConsoleColor.Green)) {
                    Log.Logger.Information("No migration script to run");
                    return;
                }
            }

            // set up the database connection
            var dialectFactory           = new DialectFactory();
            var dialect                  = dialectFactory.Create(providerName, connectionString);
            var dbProviderFactoryFactory = new DbProviderFactoryFactory();
            var dbProviderFactory        = dbProviderFactoryFactory.Create(providerName, connectionString);

            dbProviderFactory.CreateDatabaseIfNotExists(connectionString, providerName, dialect);

            // run the script
            using (var connection = dbProviderFactory.CreateConnection()) {
                connection.ConnectionString = connectionString;
                connection.Open();
                connection.Execute(script);
            }
        }
Exemple #13
0
        private string GenerateScript(IEnumerable <IMap> fromMaps, IEnumerable <IMap> configurationMaps, ISqlDialect dialect, IStatisticsProvider statisticsProvider, IAnswerProvider answerProvider, IEnumerable <string> tablesToIgnore, IEnumerable <string> indexesToIgnore)
        {
            var migrator = new Migrator(
                dialect,
                new CreateTableWriter(dialect),
                new AlterTableWriter(dialect),
                new DropTableWriter(dialect),
                statisticsProvider);
            IEnumerable <string> warnings;
            IEnumerable <string> errors;

            return(migrator.GenerateSqlDiff(
                       fromMaps,
                       configurationMaps,
                       answerProvider,
                       indexesToIgnore,
                       tablesToIgnore,
                       out warnings,
                       out errors));
        }
Exemple #14
0
        public string Generate(IConfiguration configuration, string connectionString, string providerName, IEnumerable <string> tablesToIgnore, IEnumerable <string> indexesToIgnore, IEnumerable <KeyValuePair <string, string> > extraPluralizationWords, IAnswerProvider answerProvider)
        {
            // set up the database connection
            var dialectFactory           = new DialectFactory();
            var dialect                  = dialectFactory.Create(providerName, connectionString);
            var dbProviderFactoryFactory = new DbProviderFactoryFactory();
            var dbProviderFactory        = dbProviderFactoryFactory.Create(providerName, connectionString);

            IEnumerable <IMap> fromMaps;

            if (!dbProviderFactory.DatabaseExists(connectionString, providerName, dialect))
            {
                fromMaps = Enumerable.Empty <IMap>();
                return(this.GenerateScript(fromMaps, configuration.Maps, dialect, new NullStatisticsProvider(), answerProvider, tablesToIgnore, indexesToIgnore));
            }
            else
            {
                // get the schema from the existing database
                var schemaReaderFactory         = new SchemaReaderFactory();
                var schemaReader                = schemaReaderFactory.GetSchemaReader(providerName);
                var connectionStringManipulator = new ConnectionStringManipulator(dbProviderFactory, connectionString);
                using (var connection = dbProviderFactory.CreateConnection()) {
                    connection.ConnectionString = connectionString;
                    connection.Open();
                    var schema = schemaReader.Read(connection, connectionStringManipulator.GetDatabaseName());

                    // reverse engineer the maps
                    var engineer = new Engineer(extraPluralizationWords.Union(configuration.Maps.Select(m => new KeyValuePair <string, string>(m.Type.Name, m.Table)))); // we use our configuration to inform us as to the correct naming of tables
                    fromMaps = engineer.ReverseEngineer(schema, dialect, tablesToIgnore, answerProvider, false);
                    return(this.GenerateScript(fromMaps, configuration.Maps, dialect, new StatisticsProvider(connection, dialect), answerProvider, tablesToIgnore, indexesToIgnore));
                }
            }
        }
Exemple #15
0
        public string GenerateSqlDiff(
            IEnumerable<IMap> fromMaps,
            IEnumerable<IMap> toMaps,
            IAnswerProvider answerProvider,
            ILogger logger,
            IEnumerable<string> indexesToIgnore,
            IEnumerable<string> tablesToIgnore,
            out IEnumerable<string> warnings,
            out IEnumerable<string> errors) {
            // fetch data for current database
            IDictionary<string, Statistics> currentData = new Dictionary<string, Statistics>();
            if (fromMaps.Any()) {
                currentData = this.statisticsProvider.GetStatistics(fromMaps);
            }

            var sql = new StringBuilder();
            var warningList = new List<string>();
            var errorList = new List<string>();
            var renamePrimaryKeyModifications = new Dictionary<Tuple<string, string>, bool>();
            var from = fromMaps.ToArray();
            var to = toMaps.ToArray();

            // get additions and removals
            var mapComparer = new TableNameEqualityComparer();
            var additions = to.Except(from, mapComparer).Where(m => !tablesToIgnore.Contains(m.Table)).ToList();
            var removals = from.Except(to, mapComparer).ToList();
            var matches = from.Join(to, f => f.Table.ToLowerInvariant(), t => t.Table.ToLowerInvariant(), MigrationPair.Of).ToList();

            // trace output
            logger.Trace("Additions:");
            logger.Trace("");
            logger.Trace(additions.Select(a => new { a.Table, a.Type.Name }), new[] { "Table", "Map Name" });
            logger.Trace("Removals:");
            logger.Trace("");
            logger.Trace(removals.Select(r => new { r.Table, r.Type.Name }), new[] { "Table", "Map Name" });
            logger.Trace("Matches:");
            logger.Trace("");
            logger.Trace(
                matches.Select(m => new { FromTable = m.From.Table, FromMap = m.From.Type.Name, ToTable = m.To.Table, ToMap = m.To.Type.Name }),
                new[] { "From Table", "From Map", "To Table", "To Map" });

            // look for possible entity name changes
            if (additions.Any() && removals.Any()) {
                // TODO do something a bit more sensible with regards to likelihood of rename
                foreach (var removed in removals.Select(r => r).ToArray()) {
                    // copy the array as we'll update
                    var answer =
                        answerProvider.GetMultipleChoiceAnswer(
                            string.Format("The entity {0} has been removed. If it has been renamed please specify what to:", removed.Type.Name),
                            new[] { new MultipleChoice<string> { DisplayString = "Not renamed - please delete", Choice = NoRename } }.Union(
                                additions.Select(a => new MultipleChoice<string> { Choice = a.Type.Name, DisplayString = a.Type.Name })));
                    if (answer.Choice != NoRename) {
                        // rename the table
                        var renameFrom = removed;
                        var renameTo = additions.First(a => a.Type.Name == answer.Choice);
                        sql.AppendSql(this.alterTableWriter.RenameTable(renameFrom, renameTo));

                        // add to the matches
                        matches.Add(MigrationPair.Of(renameFrom, renameTo));

                        // modify additions and removals
                        removals.Remove(renameFrom);
                        additions.Remove(renameTo);

                        // sort out the primary key
                        var fromPrimaryKey = renameFrom.PrimaryKey;
                        var toPrimaryKey = renameTo.PrimaryKey;
                        if (!AreColumnDefinitionsEqual(fromPrimaryKey, toPrimaryKey)) {
                            if (fromPrimaryKey.DbName != toPrimaryKey.DbName && fromPrimaryKey.DbType == toPrimaryKey.DbType
                                && (!fromPrimaryKey.DbType.TypeTakesLength() || (fromPrimaryKey.MaxLength && toPrimaryKey.MaxLength)
                                    || (fromPrimaryKey.Length == toPrimaryKey.Length))
                                && (!fromPrimaryKey.DbType.TypeTakesPrecisionAndScale()
                                    || (fromPrimaryKey.Precision == toPrimaryKey.Precision && fromPrimaryKey.Scale == toPrimaryKey.Scale))) {
                                // just a change in name
                                sql.AppendSql(this.alterTableWriter.ChangeColumnName(fromPrimaryKey, toPrimaryKey));
                            }
                            else {
                                // ask the question
                                // TODO may things more sensible based on the type
                                var attemptChange =
                                    answerProvider.GetBooleanAnswer(
                                        "The primary key change required for this table rename involves a change "
                                        + (fromPrimaryKey.DbType != toPrimaryKey.DbType ? "of data type" : "of specification")
                                        + ". Would you like to attempt the change? (Selecting No will drop and re-create the column)");
                                if (attemptChange) {
                                    if (fromPrimaryKey.DbName != toPrimaryKey.DbName) {
                                        sql.AppendSql(this.alterTableWriter.ChangeColumnName(fromPrimaryKey, toPrimaryKey));
                                    }

                                    sql.AppendSql(this.alterTableWriter.ModifyColumn(fromPrimaryKey, toPrimaryKey));
                                    renamePrimaryKeyModifications.Add(Tuple.Create(fromPrimaryKey.Map.Type.Name, toPrimaryKey.Map.Type.Name), true);
                                }
                                else {
                                    // drop and re-create
                                    sql.AppendSql(this.alterTableWriter.DropColumn(fromPrimaryKey));
                                    sql.AppendSql(this.alterTableWriter.AddColumn(toPrimaryKey));
                                    renamePrimaryKeyModifications.Add(Tuple.Create(fromPrimaryKey.Map.Type.Name, toPrimaryKey.Map.Type.Name), false);
                                }
                            }
                        }
                    }
                }
            }

            // do removal of foreign keys and indexes that we don't need
            // only do this on remaining tables as drops should be deleted automatically
            foreach (var matchPair in matches) {
                var fkRemovals = matchPair.From.ForeignKeys.Except(matchPair.To.ForeignKeys);
                foreach (var foreignKey in fkRemovals) {
                    sql.AppendSql(this.alterTableWriter.DropForeignKey(foreignKey));
                }

                var indexRemovals = matchPair.From.Indexes.Except(matchPair.To.Indexes);
                foreach (var index in indexRemovals.Where(i => !indexesToIgnore.Contains(i.Name))) {
                    sql.AppendSql(this.alterTableWriter.DropIndex(index));
                }
            }

            // do renames of columns
            var columnKeyValuePairEqualityComparer = new ColumnKeyValuePairEqualityComparer();
            var addedProperties = new List<IColumn>();
            foreach (var pair in matches) {
                var fromCols = pair.From.OwnedColumns(true).ToDictionary(c => c.DbName, c => c);
                var toCols = pair.To.OwnedColumns(true).ToDictionary(c => c.DbName, c => c);

                var removedColumns = fromCols.Except(toCols, columnKeyValuePairEqualityComparer);
                var addedColumns = toCols.Except(fromCols, columnKeyValuePairEqualityComparer).ToList();
                if (removedColumns.Any()) {
                    // handle drops and renames
                    foreach (var removal in removedColumns) {
                        if (pair.From.Type.Name != pair.To.Type.Name && removal.Value.IsPrimaryKey) {
                            // ignore this one and get the new pk and remove it as handled above
                            var newPrimaryKey = toCols.Single(c => c.Value.IsPrimaryKey);
                            addedColumns.Remove(newPrimaryKey);
                            continue;
                        }

                        if (addedColumns.Any()) {
                            var answer =
                                answerProvider.GetMultipleChoiceAnswer(
                                    string.Format(
                                        "The property {0} has been removed. If it has been renamed please specify what to:",
                                        removal.Value.Name),
                                    new[] { new MultipleChoice<string> { DisplayString = "Not renamed - please delete", Choice = NoRename } }.Union(
                                        addedColumns.Select(a => new MultipleChoice<string> { Choice = a.Value.Name, DisplayString = a.Value.Name })));
                            if (answer.Choice == NoRename) {
                                // drop the column
                                if (pair.From.Type.Name != pair.To.Type.Name) {
                                    removal.Value.Map = pair.To; // want to delete from the correctly named table in the event of a rename
                                }

                                sql.AppendSql(this.alterTableWriter.DropColumn(removal.Value));
                            }
                            else {
                                // rename the column
                                var toColumn = addedColumns.First(c => c.Value.Name == answer.Choice);
                                sql.AppendSql(this.alterTableWriter.ChangeColumnName(removal.Value, toColumn.Value));

                                // if need be perform a modify statement
                                if (this.RequiresColumnSpecificationChange(removal.Value, toColumn.Value)) {
                                    sql.AppendSql(this.alterTableWriter.ModifyColumn(removal.Value, toColumn.Value));
                                }

                                // remove the column from the additions
                                addedColumns.Remove(toColumn);
                            }
                        }
                        else {
                            // drop the column
                            if (pair.From.Type.Name != pair.To.Type.Name) {
                                removal.Value.Map = pair.To; // want to delete from the correctly named table in the event of a rename
                            }

                            sql.AppendSql(this.alterTableWriter.DropColumn(removal.Value));
                        }
                    }
                }

                // go through existing columns and handle modifications
                foreach (var fromProp in pair.From.Columns) {
                    logger.Trace("Examining {1}.{0}", fromProp.Value.Name, pair.From.Table);
                    var matchingToProp = pair.To.Columns.Select(p => p.Value).FirstOrDefault(p => p.Name == fromProp.Key);
                    if (matchingToProp != null) {
                        if (this.RequiresColumnSpecificationChange(fromProp.Value, matchingToProp)) {
                            // check for potential errors
                            if (fromProp.Value.DbType != matchingToProp.DbType) {
                                bool skipQuestion = false;
                                bool wasPrimaryKeyDroppedAndRecreated = false;
                                var renamePrimaryKeyModificationsKey =
                                    Tuple.Create(
                                        fromProp.Value.Relationship == RelationshipType.ManyToOne
                                            ? fromProp.Value.ParentMap.Type.Name
                                            : fromProp.Value.OppositeColumn.ParentMap.Type.Name,
                                        matchingToProp.Relationship == RelationshipType.ManyToOne
                                            ? matchingToProp.ParentMap.Type.Name
                                            : matchingToProp.OppositeColumn.Map.Type.Name);
                                if ((fromProp.Value.Relationship == RelationshipType.OneToOne
                                     || fromProp.Value.Relationship == RelationshipType.ManyToOne)
                                    && (matchingToProp.Relationship == RelationshipType.ManyToOne
                                        || matchingToProp.Relationship == RelationshipType.OneToOne)
                                    && renamePrimaryKeyModifications.ContainsKey(renamePrimaryKeyModificationsKey)) {
                                    // skip the question as we've already attempted the modify for the pk so may as well here as well!
                                    skipQuestion = true;
                                    wasPrimaryKeyDroppedAndRecreated = !renamePrimaryKeyModifications[renamePrimaryKeyModificationsKey];
                                }

                                bool dropAndRecreate = wasPrimaryKeyDroppedAndRecreated;
                                if (!skipQuestion) {
                                    dropAndRecreate =
                                        answerProvider.GetBooleanAnswer(
                                            string.Format(
                                                "Attempting to change DbType for property {0} on {1} from {2} to {3}. Would you like to attempt the change? (selecting \"No\" will drop and re-create the column)",
                                                matchingToProp.Name,
                                                matchingToProp.Map.Type.Name,
                                                fromProp.Value.DbType,
                                                matchingToProp.DbType));
                                }

                                if (dropAndRecreate) {
                                    sql.AppendSql(this.alterTableWriter.DropColumn(matchingToProp));
                                    sql.AppendSql(this.alterTableWriter.AddColumn(matchingToProp));
                                    continue;
                                }

                                warningList.Add(
                                    string.Format(
                                        "Changing DB Type is not guaranteed to work: {0} on {1}",
                                        fromProp.Value.Name,
                                        fromProp.Value.Map.Type.Name));
                            }

                            if ((this.RequiresLengthChange(fromProp.Value, matchingToProp)
                                 && (fromProp.Value.MaxLength || fromProp.Value.Length < matchingToProp.Length))
                                || (this.RequiresPrecisionOrScaleChange(fromProp.Value, matchingToProp)
                                    && (fromProp.Value.Precision > matchingToProp.Precision || fromProp.Value.Scale > matchingToProp.Scale))) {
                                warningList.Add(
                                    string.Format(
                                        "{0} on {1} is having its precision, scale or length reduced. This may result in loss of data",
                                        fromProp.Value.Name,
                                        fromProp.Value.Map.Type.Name));
                            }

                            sql.AppendSql(this.alterTableWriter.ModifyColumn(fromProp.Value, matchingToProp));
                        }
                        else {
                            if ((matchingToProp.Relationship == RelationshipType.ManyToOne || matchingToProp.Relationship == RelationshipType.OneToOne)
                                && (fromProp.Value.Relationship == RelationshipType.ManyToOne
                                    || fromProp.Value.Relationship == RelationshipType.OneToOne)
                                && fromProp.Value.Type.Name != matchingToProp.Type.Name && currentData != null
                                && currentData[matchingToProp.Map.Type.Name].HasRows) {
                                warningList.Add(
                                    string.Format(
                                        "Property {0} on {1} has changed type but the column was not dropped. There is data in that table, please empty that column if necessary",
                                        matchingToProp.Name,
                                        matchingToProp.Map.Type.Name));
                            }
                        }
                    }
                }

                // add the added columns to the addedProperties list
                addedProperties.AddRange(addedColumns.Select(c => c.Value));
            }

            // do deletes of entities
            foreach (var removal in removals) {
                sql.AppendSql(this.dropTableWriter.DropTable(removal));
            }

            // do additions of entities
            foreach (var addition in additions) {
                sql.AppendSql(this.createTableWriter.CreateTable(addition));
            }

            // do additions of properties
            foreach (var newProp in addedProperties) {
                // check for relationships where the related table is not empty and the prop is not null
                if ((newProp.Relationship == RelationshipType.ManyToOne || newProp.Relationship == RelationshipType.OneToOne) && !newProp.IsNullable
                    && string.IsNullOrWhiteSpace(newProp.Default) && currentData.ContainsKey(newProp.Map.Type.Name)
                    && currentData[newProp.Map.Type.Name].HasRows) {
                    var foreignKeyPrimaryKeyType = newProp.Relationship == RelationshipType.ManyToOne
                                                       ? newProp.ParentMap.PrimaryKey.Type
                                                       : newProp.OppositeColumn.Map.PrimaryKey.Type;
                    var answer = answerProvider.GetType()
                                               .GetMethod("GetAnswer")
                                               .MakeGenericMethod(foreignKeyPrimaryKeyType)
                                               .Invoke(
                                                   answerProvider,
                                                   new object[] {
                                                                    string.Format(
                                                                        "You are adding a property {0} on {1} that has a foreign key to {2} (which is non-empty) with primary key type {3}. Please specify a default value for the column:",
                                                                        newProp.Name,
                                                                        newProp.Map.Type.Name,
                                                                        newProp.Type.Name,
                                                                        foreignKeyPrimaryKeyType)
                                                                });
                    newProp.Default = answer.ToString();
                }

                sql.AppendSql(this.alterTableWriter.AddColumn(newProp));
            }

            // add in new foreign keys for additions
            foreach (var map in additions) {
                var statements = this.createTableWriter.CreateForeignKeys(map);
                foreach (var statement in statements) {
                    sql.AppendSql(statement);
                }
            }

            // add in new indexes for additions
            foreach (var map in additions) {
                var statements = this.createTableWriter.CreateIndexes(map);
                foreach (var statement in statements) {
                    sql.AppendSql(statement);
                }
            }

            // add in missing foreign keys and indexes
            foreach (var matchPair in matches) {
                var fkAdditions = matchPair.To.ForeignKeys.Except(matchPair.From.ForeignKeys);
                var fkStatements = this.createTableWriter.CreateForeignKeys(fkAdditions);
                foreach (var statement in fkStatements) {
                    sql.AppendSql(statement);
                }

                var indexAdditions = matchPair.To.Indexes.Except(matchPair.From.Indexes);
                var indexStatements = this.createTableWriter.CreateIndexes(indexAdditions);
                foreach (var statement in indexStatements) {
                    sql.AppendSql(statement);
                }
            }

            warnings = warningList;
            errors = errorList;
            return sql.ToString();
        }