Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete.Table.IfExists #494

Closed
katlimruiz opened this issue Apr 26, 2014 · 12 comments
Closed

Delete.Table.IfExists #494

katlimruiz opened this issue Apr 26, 2014 · 12 comments
Labels
feature A new feature (we all like those)
Milestone

Comments

@katlimruiz
Copy link
Contributor

All DROP TABLE commands should do a If Exists check first (I'm using Sql Server 2012).

@tommarien
Copy link
Contributor

@katlimruiz I beg to differ, the entire idea of doing migrations is expecting your database to be in a given state. If you execute a drop table, you do it because you know the table exists

@tommarien
Copy link
Contributor

But maybe adding an if exists to the fluent api could give the user a choice, but i am agains auto opt-in

@colinangusmackay
Copy link

The user should at least have a choice of doing if-exists style checks before certain operations.

I agree that you should expect the database to be in a certain state, but reality is that on any given project many people may be writing migrations, interfering with each other and having to go back to alter existing migrations after others have already run in the migration then updating migration numbers and all sorts of things in order to get the job done quicker. Or simply because they are a junior member of the team and they make mistakes. Or because various people in various branches of development are looking at different views of the database with different sets of migrations run in. At the end of all that the migrations are messier but they work over a wide range of systems in various initial states including each developers' personal database, the test, UAT, DEMO, Release-Candidate and, of course, live databases.

@eldersantos
Copy link

I vote for a IfExists on a drop table, I agree in the case the database sometimes is not perfect as expected.
But, to be consistency with the migration library, it should be included to the other db objects, like Indexes, contraints, Views, etc

@migol
Copy link

migol commented Aug 2, 2014

I would differ on it as well, I'm working on a very big heathcare project and we just switched to using FluentMigrator as our database change support. Problem is that lots of hospitals have differences in databases and we need sometimes to remove a table if it exists.

I agree with @eldersantos that DeleteIfExists should be supported across all the schema elements.

@tommarien
Copy link
Contributor

@migol @eldersantos Like already said i am open to accept a pull considering this ;)

@jordan-ware
Copy link

For those working with SQL Server, in the mean-time you can create an extension method like below:

public static void DropTableIfExists(this IExecuteExpressionRoot execute, string tableName)
{
   execute.Sql(string.Format("IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '{0}')) BEGIN DROP TABLE [{0}] END", tableName));
}

And call it: Execute.DropTableIfExists("TableName");

It falls short of a real solution where all database providers are considered, and does not take schemas into consideration.

@eloekset
Copy link
Member

I copy stuff like this around all my migration steps:

        private void AddForeignKeyConstraintIfNotExists(string foreignKeyTable, string foreignKey, string primaryKeyTable, string primaryKey)
        {
            string fkName = $"FK_{foreignKeyTable}_{primaryKeyTable}";

            if (!ForeignKeyExists(fkName, foreignKeyTable))
            {
                Create.ForeignKey(fkName)
                    .FromTable(foreignKeyTable).InSchema(TdmSchema).ForeignColumn(foreignKey)
                    .ToTable(primaryKeyTable).InSchema(TdmSchema).PrimaryColumn(primaryKey);
            }
        }

        private void CreateTdmSchemaIfNotExists()
        {
            if (!Schema.Schema(TdmSchema).Exists())
            {
                Create.Schema(TdmSchema);
            }
        }

        private bool ColumnExists(string tableName, string columnName)
        {
            return Schema.Schema(TdmSchema).Table(tableName).Column(columnName).Exists();
        }

        private bool TableExists(string tableName)
        {
            return Schema.Schema(TdmSchema).Table(tableName).Exists();
        }

        private bool ForeignKeyExists(string foreignKeyName, string foreignKeyTableName)
        {
            return Schema.Schema(TdmSchema).Table(foreignKeyTableName).Constraint(foreignKeyName).Exists();
        }

I even use more specialized helper methods to create a clustered key different from primary keys for tables that have uniqueidentifier as PK:

        private void CreateDefaultsPrimaryKeyClusteredAndAccountIndex(string tableName, string primaryKeyColumnName)
        {
            Execute.Sql($"ALTER TABLE [{TdmSchema}].[{tableName}] ADD CONSTRAINT [DF_{tableName}_{primaryKeyColumnName}] DEFAULT (NewID()) FOR [{primaryKeyColumnName}]");
            Execute.Sql($"ALTER TABLE [{TdmSchema}].[{tableName}] ADD CONSTRAINT [DF_{tableName}_CreatedAt] DEFAULT (GetUtcDate()) FOR CreatedAt");
            Execute.Sql($"CREATE CLUSTERED INDEX [CI_{tableName}] ON [{TdmSchema}].[{tableName}] (CreatedAt)");
            Execute.Sql($"ALTER TABLE [{TdmSchema}].[{tableName}] ADD CONSTRAINT [PK_{tableName}] PRIMARY KEY ([{primaryKeyColumnName}])");
            Create.Index($"[IDX_{tableName}_AccountId]").OnTable(tableName).InSchema(TdmSchema).OnColumn(AccountId).Ascending();
        }

        private void CreatePrimaryKeysClusteredAndAccountIndex(string tableName, List<string> primaryKeyColumns)
        {
            string joinedColumns = string.Join(",", primaryKeyColumns.Select(c => $"[{c}]").ToList());
            Execute.Sql($"ALTER TABLE [{TdmSchema}].[{tableName}] ADD CONSTRAINT [DF_{tableName}_CreatedAt] DEFAULT (GetUtcDate()) FOR CreatedAt");
            Execute.Sql($"CREATE CLUSTERED INDEX [CI_{tableName}] ON [{TdmSchema}].[{tableName}] (CreatedAt)");
            Execute.Sql($"ALTER TABLE [{TdmSchema}].[{tableName}] ADD CONSTRAINT [PK_{tableName}] PRIMARY KEY ({joinedColumns})");
            Create.Index($"[IDX_{tableName}_AccountId]").OnTable(tableName).InSchema(TdmSchema).OnColumn(AccountId).Ascending();
        }

        private ICreateTableColumnOptionOrWithColumnSyntax WithCreatedAtColumn(ICreateTableColumnOptionOrWithColumnSyntax createTableSyntax)
        {
            return createTableSyntax.WithColumn(CreatedAt).AsDateTimeOffset();  // Default constraint added along with PK
        }

That makes it easy to do stuff like:

        private void CreateForeignKeysIfNotExists()
        {
            AddForeignKeyConstraintIfNotExists(InvoiceLine, "InvoiceId", Invoice, "InvoiceId");
            AddForeignKeyConstraintIfNotExists(InvoiceDocument, "InvoiceId", Invoice, "InvoiceId");
            AddForeignKeyConstraintIfNotExists(OrderDocument, "OrderId", Order, "OrderId");
        }

        private void CreateTablesIfNotExists()
        {
            if (!TableExists(Invoice))
            {
                var invoiceTable = Create.Table(Invoice).InSchema(TdmSchema)
                    .WithColumn(AccountId).AsGuid().NotNullable()
                    .WithColumn("InvoiceId").AsGuid().NotNullable()
                    .WithColumn("InvoiceDate").AsDateTimeOffset().NotNullable()
                    .WithColumn("InvoiceDateString").AsString(10).NotNullable()
                    .WithColumn("InvoiceNo").AsInt32().NotNullable()
                    .WithColumn("ContactPersonId").AsGuid().Nullable()
                    .WithColumn("ContactPerson").AsString(255).Nullable()
                    .WithColumn("DueDate").AsDateTimeOffset().Nullable()
                    .WithColumn("CurrencyCode").AsString(7).Nullable()
                    .WithColumn("CustomerId").AsGuid().Nullable()
                    .WithColumn("CustomerNo").AsInt32().Nullable()
                    .WithColumn("ResponsibleId").AsGuid().Nullable()
                    .WithColumn("Responsible").AsString(255).Nullable();

                invoiceTable = WithCreatedAtColumn(invoiceTable);
                CreateDefaultsPrimaryKeyClusteredAndAccountIndex(Invoice, "InvoiceId");
            }
    }

That's what I like about FluentMigrator as opposed of writing SQL scripts by hand. It's so easy to write reusable helper methods for common patterns in C# and make things a bit more typesafe and less error prone.

@fubar-coder
Copy link
Member

Querying the current state of the database doesn't work well with SQL script generation. We have to find a middle ground between PR #664 and this solution.

@fubar-coder fubar-coder added feature A new feature (we all like those) change-required-complex labels Mar 31, 2018
@fubar-coder fubar-coder added this to the Backlog milestone Mar 31, 2018
@jzabroski
Copy link
Collaborator

@fubar-coder
Some databases have begun adding "DROP TABLE IF EXISTS [schema].[table]" syntax.

I think if the ask is to support this syntax, the complexity is low.

Note: I had a real use case for this just now :)

I have a customer with tables named SomeData_2018Q2, SomeData_2018Q3, etc. that are driven by a quarterly process. However, in my test environment, that quarterly process doesn't run, making it annoying to run migrations since I have to tag them with specific environments as opposed to running the same migrations in all environments.

@jzabroski
Copy link
Collaborator

Workaround:

        private void DropTableIfExists(string schemaName, string tableName)
        {
            this.Execute.Sql($"DROP TABLE IF EXISTS [{schemaName}].[{tableName}];");
        }

@jzabroski jzabroski modified the milestones: Backlog, 4.0.0 Jul 19, 2022
@jzabroski jzabroski modified the milestones: 4.0.0, 5.0.0 Jan 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature A new feature (we all like those)
Projects
None yet
Development

No branches or pull requests

9 participants