EF migration for changing data type of columns

55,062

Solution 1

You have a default constraint on your column. You need to first drop the constraint, then alter your column.

public override void Up()
{
    Sql("ALTER TABLE dbo.Received DROP CONSTRAINT DF_Receiv_FromN__25869641");
    AlterColumn("dbo.Received", "FromNo", c => c.String());
    AlterColumn("dbo.Received", "ToNo", c => c.String());
    AlterColumn("dbo.Received", "TicketNo", c => c.String());
}

You will probably have to drop the default constraints on your other columns as well.

I've just seen Andrey's comment (I know - very late) and he is correct. So a more robust approach would be to use something like:

 DECLARE @con nvarchar(128)
 SELECT @con = name
 FROM sys.default_constraints
 WHERE parent_object_id = object_id('dbo.Received')
 AND col_name(parent_object_id, parent_column_id) = 'FromNo';
 IF @con IS NOT NULL
     EXECUTE('ALTER TABLE [dbo].[Received] DROP CONSTRAINT ' + @con)

I know this probably doesn't help the OP but hopefully it helps anyone else that comes across this issue.

Solution 2

static internal class MigrationExtensions
{
    public static void DeleteDefaultConstraint(this IDbMigration migration, string tableName, string colName, bool suppressTransaction = false)
    {
        var sql = new SqlOperation(
            string.Format(@"DECLARE @SQL varchar(1000)
                            SET @SQL='ALTER TABLE {0} DROP CONSTRAINT ['+(SELECT name
                            FROM sys.default_constraints
                            WHERE parent_object_id = object_id('{0}')
                            AND col_name(parent_object_id, parent_column_id) = '{1}')+']';
                            PRINT @SQL;
                            EXEC(@SQL);", tableName, colName)
            )
        {
            SuppressTransaction = suppressTransaction
        };
        migration.AddOperation(sql);
    }
}

public override void Up()
{
    this.DeleteDefaultConstraint("dbo.Received", "FromNo");
    AlterColumn("dbo.Received", "FromNo", c => c.String());
    this.DeleteDefaultConstraint("dbo.Received", "ToNo");
    AlterColumn("dbo.Received", "ToNo", c => c.String());
    this.DeleteDefaultConstraint("dbo.Received", "TicketNo");
    AlterColumn("dbo.Received", "TicketNo", c => c.String());
}

Solution 3

The better way is to solve the problem for ever.

You can implement a custom sql generator class derived from SqlServerMigrationSqlGenerator from System.Data.Entity.SqlServer namespace:

using System.Data.Entity.Migrations.Model;
using System.Data.Entity.SqlServer;

namespace System.Data.Entity.Migrations.Sql{
    internal class FixedSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator {
        protected override void Generate(AlterColumnOperation alterColumnOperation){
            ColumnModel column = alterColumnOperation.Column;
            var sql = String.Format(@"DECLARE @ConstraintName varchar(1000);
            DECLARE @sql varchar(1000);
            SELECT @ConstraintName = name   FROM sys.default_constraints
                WHERE parent_object_id = object_id('{0}')
                AND col_name(parent_object_id, parent_column_id) = '{1}';
            IF(@ConstraintName is NOT Null)
                BEGIN
                set @sql='ALTER TABLE {0} DROP CONSTRAINT [' + @ConstraintName+ ']';
            exec(@sql);
            END", alterColumnOperation.Table, column.Name);
                this.Statement(sql);
            base.Generate(alterColumnOperation);
            return;
        }
        protected override void Generate(DropColumnOperation dropColumnOperation){
            var sql = String.Format(@"DECLARE @SQL varchar(1000)
                SET @SQL='ALTER TABLE {0} DROP CONSTRAINT [' + (SELECT name
                    FROM sys.default_constraints
                    WHERE parent_object_id = object_id('{0}')
                    AND col_name(parent_object_id, parent_column_id) = '{1}') + ']';
            PRINT @SQL;
                EXEC(@SQL); ", dropColumnOperation.Table, dropColumnOperation.Name);

                    this.Statement(sql);
            base.Generate(dropColumnOperation);
        }
    }
}

and Set this configuration:

internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = true;

        SetSqlGenerator("System.Data.SqlClient", new FixedSqlServerMigrationSqlGenerator ());
    }
    ...
}

Solution 4

This is an example for changing an existing column to 'not null' that already has a foreign key constraint. The column's name is "FKColumnName" in table "SubTable" and it is referencing the "Id" column in table "MainTable".

Up script:

After the column is made 'not nullable' the index and the foreign key first dropped and then re-created.

Down script:

Here the steps are identical except that the column is made nullable again.

public partial class NameOfMigration : DbMigration
{
    public override void Up()
    {
        DropForeignKey("dbo.SubTable", "FKColumnName", "dbo.MainTable");
        DropIndex("dbo.SubTable", new[] { "FKColumnName" });

        AlterColumn("dbo.SubTable", "FKColumnName", c => c.Int(nullable: false));

        CreateIndex("dbo.SubTable", "FKColumnName");
        AddForeignKey("dbo.SubTable", "FKColumnName", "dbo.MainTable", "Id");
    }

    public override void Down()
    {
        DropForeignKey("dbo.SubTable", "FKColumnName", "dbo.MainTable");
        DropIndex("dbo.SubTable", new[] { "FKColumnName" });

        AlterColumn("dbo.SubTable", "FKColumnName", c => c.Int(nullable: true));

        CreateIndex("dbo.SubTable", "FKColumnName");
        AddForeignKey("dbo.SubTable", "FKColumnName", "dbo.MainTable", "Id");
    }
}

Solution 5

I was having this issue with a default value of zero constraint on an integer column.

In my case I solved it by switching from Entity Framework 6.1.x to EF 6.2.0.

There's a known bug in EF prior to 6.2 that means that EF sometimes doesn't deal with these types of constraints automatically when altering columns. That bug is described on the official EF github repo here, Bricelam describes the issue as:

When adding NOT NULL columns, we synthesize a default value for any existing rows. It looks like our logic to drop default constraints before ALTER COLUMN doesn't take this into account.

The commit for the fix for that issue can be found here.

Share:
55,062
Pooya Yazdani
Author by

Pooya Yazdani

Updated on August 13, 2020

Comments

  • Pooya Yazdani
    Pooya Yazdani over 3 years

    I have a Model in my project as below:

    public class Model 
    {
        public int Id { get; set; }
        public long FromNo { get; set; }
        public long ToNo { get; set; }
        public string Content { get; set; }
        public long TicketNo { get; set; }
    }
    

    The migration is as below

    public override void Down()
    {
        AlterColumn("dbo.Received", "FromNo", c => c.Long(nullable: false));
        AlterColumn("dbo.Received", "ToNo", c => c.Long(nullable: false));
        AlterColumn("dbo.Received", "TicketNo", c => c.Long(nullable: false));
    }
    public override void Up()
    {
        AlterColumn("dbo.Received", "FromNo", c => c.String());
        AlterColumn("dbo.Received", "ToNo", c => c.String());
        AlterColumn("dbo.Received", "TicketNo", c => c.String());
    }
    

    when I use Update-Database the error below is raised:

    The object 'DF__Receiv__FromN__25869641' is dependent on column 'FromNo'. ALTER TABLE ALTER COLUMN FromNo failed because one or more objects access this column.

    This tables has no foreign key or what else so what is the problem?