web-dev-qa-db-fra.com

Migration EF pour changer le type de données des colonnes

J'ai un modèle dans mon projet comme ci-dessous:

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

La migration est comme ci-dessous

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

lorsque j'utilise Update-Database, l'erreur ci-dessous est générée:

L'objet 'DF__Receiv__FromN__25869641' dépend de la colonne 'FromNo'. ALTER TABLE ALTER COLUMN FromNo a échoué car un ou plusieurs objets accèdent à cette colonne.

Cette table n'a pas de clé étrangère ou quoi d'autre alors quel est le problème?

48
Pooya Yazdani

Vous avez une contrainte par défaut sur votre colonne. Vous devez d'abord supprimer la contrainte, puis modifier votre colonne.

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

Vous devrez probablement supprimer également les contraintes par défaut sur vos autres colonnes.

Je viens de voir le commentaire d'Andrey (je sais - très tard) et il a raison. Une approche plus robuste consisterait donc à utiliser quelque chose comme:

 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)

Je sais que cela n'aide probablement pas le PO, mais j'espère que cela aide toute autre personne qui rencontre ce problème.

74
James Hull
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());
}
41
DTTerastar

Ceci est un exemple pour changer une colonne existante en 'non nul' qui a déjà une contrainte de clé étrangère. Le nom de la colonne est "FKColumnName" dans la table "SubTable" et fait référence à la colonne "Id" dans la table "MainTable".

Script supérieur:

Une fois que la colonne est rendue "non nulle", l'index et la clé étrangère ont d'abord été supprimés, puis recréés.

Down script:

Ici, les étapes sont identiques, sauf que la colonne est de nouveau nullable.

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");
    }
}
2
Martin

La meilleure façon est de résoudre le problème pour toujours.

Vous pouvez implémenter une classe de générateur SQL personnalisée dérivée de SqlServerMigrationSqlGenerator à partir de l'espace de noms System.Data.Entity.SqlServer:

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

et définissez cette configuration:

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

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

J'avais ce problème avec une valeur par défaut de contrainte zéro sur une colonne entière.

Dans mon cas, je l'ai résolu en passant d'Entity Framework 6.1.x à EF 6.2.0.

Il y a un bogue connu dans EF avant 6.2 qui signifie que EF ne traite pas automatiquement ces types de contraintes lors de la modification des colonnes. Ce bug est décrit sur le repo officiel EF github ici , Bricelam décrit le problème comme:

Lors de l'ajout de colonnes NOT NULL, nous synthétisons une valeur par défaut pour toutes les lignes existantes. Il semble que notre logique consiste à supprimer les contraintes par défaut avant qu'ALTER COLUMN n'en tienne compte.

Le commit du correctif pour ce problème peut être trouvé ici .

0
tomRedox