web-dev-qa-db-fra.com

Comment puis-je changer une colonne ID int en Guid avec migration EF?

J'utilise l'approche par code EF et je souhaite changer le champ Id en guid mais je n'arrive pas à surmonter l'erreur ci-dessous.

Ceci est ma première migration:

public partial class CreateDownloadToken : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.DownloadTokens",
            c => new
            {
                Id = c.Int(nullable: false, identity: true),
                FileId = c.Int(),
                UserId = c.String(nullable: false, maxLength: 128),
                ValidUntil = c.DateTime(nullable: false),
            })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Files", t => t.FileId)
            .ForeignKey("dbo.Users", t => t.UserId, cascadeDelete: true)
            .Index(t => t.FileId)
            .Index(t => t.UserId);

    }

    public override void Down()
    {
        DropForeignKey("dbo.DownloadTokens", "UserId", "dbo.Users");
        DropForeignKey("dbo.DownloadTokens", "FileId", "dbo.Files");
        DropIndex("dbo.DownloadTokens", new[] { "UserId" });
        DropIndex("dbo.DownloadTokens", new[] { "FileId" });
        DropTable("dbo.DownloadTokens");
    }
}

Plus tard, j'ai réalisé que j'avais besoin que ma colonne Id soit GUID alors j'ai changé mon fichier modèle:

public class DownloadToken
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public Guid Id { get; set; }

    public int? FileId { get; set; }

    [ForeignKey("FileId")]
    public virtual File File { get; set; }

    [Required]
    public string UserId { get; set; }

    [ForeignKey("UserId")]
    public virtual User User { get; set; }

    [Required]
    public DateTime ValidUntil { get; set; }
}

Lors de l'exécution Add-Migration ChangeDownloadTokenIdToGuid il génère ce fichier:

public partial class ChangeDownloadTokenIdToGuid : DbMigration
{
    public override void Up()
    {
        DropPrimaryKey("dbo.DownloadTokens");
        AlterColumn("dbo.DownloadTokens", "Id", c => c.Guid(nullable: false));
        AddPrimaryKey("dbo.DownloadTokens", "Id");
    }

    public override void Down()
    {
        DropPrimaryKey("dbo.DownloadTokens");
        AlterColumn("dbo.DownloadTokens", "Id", c => c.Int(nullable: false, identity: true));
        AddPrimaryKey("dbo.DownloadTokens", "Id");
    }
}

Exécution de ce fichier avec Update-Database provoque cette erreur:

Identity column 'Id' must be of data type int, bigint, smallint, tinyint, or decimal or numeric with a scale of 0, and constrained to be nonnullable.

Des idées pourquoi cela pourrait se produire?

18
karlingen

Cela est dû au fait qu'il est impossible de convertir le type int précédent de la colonne Id en type Guid (qui essaie exactement d'exécuter la méthode AlterColumn). De plus, un message d'erreur vous suggère que le nouveau type de colonne Id peut être de type set: int, bigint, smallint, tinyint, ou décimal ou numérique avec une échelle de 0 , pour eux, il est possible d'effectuer une conversion de type int.

Solution - déposez simplement la colonne Id puis recréez-la avec le nouveau type Guid, changez la migration de cette façon:

public partial class ChangeDownloadTokenIdToGuid : DbMigration
{
    public override void Up()
    {
        DropPrimaryKey("dbo.DownloadTokens");

        DropColumn("dbo.DownloadTokens", "Id");
        AddColumn("dbo.DownloadTokens", "Id", c => c.Guid(nullable: false, identity: true));

        AddPrimaryKey("dbo.DownloadTokens", "Id");
    }

    public override void Down()
    {
        DropPrimaryKey("dbo.DownloadTokens");

        DropColumn("dbo.DownloadTokens", "Id");
        AddColumn("dbo.DownloadTokens", "Id", c => c.Int(nullable: false, identity: true));

        AddPrimaryKey("dbo.DownloadTokens", "Id");
    }
}

P.S. Pourquoi vous utilisez DatabaseGeneratedOption.Computed attribut, pas DatabaseGeneratedOption.Identity?

26
Slava Utesinov

Même si Slava Utesinov fonctionne, il ne fonctionne que sur des tables vides ou dans les cas où aucune autre table ne fait référence à la table que vous convertissez. Cette réponse aidera donc ceux qui se retrouvent sur cette page avec une configuration de base de données plus complexe.

Vous trouverez ci-dessous une fonction utilitaire que vous pouvez utiliser à partir de votre classe de migration, qui doit être appelée à partir des fonctions Up/Down. La fonction gère également les tables faisant référence à la table que vous essayez de convertir d'Int en Guid. Cette fonction d'assistance suppose que la colonne que vous convertissez s'appelle 'Id', mais devrait sinon être plutôt générique.

public void Convert(bool toGuid, string parent, params string[] children)
    {
        if (toGuid)
        {
            AddColumn($"dbo.{parent}s", "Id2", c => c.Guid(nullable: false, identity: true, defaultValueSql: "newid()"));
        }
        else
        {
            AddColumn($"dbo.{parent}s", "Id2", c => c.Int(nullable: false, identity: true));
        }
        foreach (var child in children)
        {
            DropForeignKey($"dbo.{child}s", $"{parent}_Id", $"dbo.{parent}s");
            DropIndex($"dbo.{child}s", new[] { $"{parent}_Id" });
            RenameColumn($"dbo.{child}s", $"{parent}_Id", $"old_{parent}_Id");
            if (toGuid)
            {
                AddColumn($"dbo.{child}s", $"{parent}_Id", c => c.Guid());
            }
            else
            {
                AddColumn($"dbo.{child}s", $"{parent}_Id", c => c.Int());
            }
            Sql($"update c set {parent}_Id=p.Id2 from {child}s c inner join {parent}s p on p.Id=c.old_{parent}_Id");
            DropColumn($"dbo.{child}s", $"old_{parent}_Id");
        }
        DropPrimaryKey($"dbo.{parent}s");
        DropColumn($"dbo.{parent}s", "Id");
        RenameColumn($"dbo.{parent}s", "Id2", "Id");
        AddPrimaryKey($"dbo.{parent}s", "Id");
        foreach (var child in children)
        {
            CreateIndex($"dbo.{child}s", $"{parent}_Id");
            AddForeignKey($"dbo.{child}s", $"{parent}_Id", $"dbo.{parent}s", "Id");
        }
    }

Donc dans votre cas, vos fonctions Up/Down seraient:

    public override void Up()
    {
        Convert(true,"DownloadToken");
    }

    public override void Down()
    {
        Convert(false, "DownloadToken");
    }
12
Andreas Willadsen