在代码优先迁移中使用现有行将唯一索引添加到 table 时处理重复项

Handling duplicates when adding unique index to table with existing rows in code first migration

我想使用代码优先迁移向现有 table 添加一个具有唯一索引的新列。因为 table 已经有数据,所以在创建新列时,所有现有行都设置为 NULL。迁移应用失败,因为这违反了新索引的唯一约束。

这是我的迁移:

public override void Up()
{
    AddColumn("dbo.SignInToken", "Token", c => c.String(maxLength: 32));
    // How can I update existing rows here?
    CreateIndex("dbo.SignInToken", "Token", unique: true);
}

错误:

The CREATE UNIQUE INDEX statement terminated because a duplicate key was found for the object name 'dbo.SignInToken' and the index name 'IX_Token'. The duplicate key value is ().

新列将包含我在代码中设置的随机生成的字符串。

我知道我可以使用 Sql 方法在迁移中执行原始 SQL。我想过使用游标来更新每一行,但随后我将不得不编写一个存储过程来模仿生成随机标记的 C# 方法。我宁愿不必这样做。我想到的另一件事是,如果有一种方法可以检索所有行,我可以循环遍历它们并使用 Sql 方法对每一行执行更新语句,但我不知道这是不是可能。

有没有办法在添加唯一约束之前从迁移中单独更新所有现有行?

一种解决方案是将主键复制到新列中。如果需要,可以在种子方法中更新。

更新迁移:

public override void Up()
{
    AddColumn("dbo.SignInToken", "Token", c => c.String(maxLength: 40));
    Sql("UPDATE dbo.SignInToken SET Token = Id");
    CreateIndex("dbo.SignInToken", "Token", unique: true);
}

然后,如果需要,您可以使用 Seed 方法中的 DbContext 来更新行。需要一些逻辑来确保它不会在未来的迁移中覆盖新数据(例如,仅在 Id == Token 时更新行)。

此外,如果您没有现有的唯一列(复制它的值)并且想要将唯一列添加到已经有一些行的 table 中,您可以尝试这种方法

AddColumn("dbo.SignInToken", 
          "Token", 
          c => c.Int(nullable: false,
                     defaultValueSql: "CONVERT(int, CONVERT(VARBINARY(16), NEWID(), 1))"));

CreateIndex("dbo.IriSqlServerLayer", "Order", unique: true, name: "IX_UniqueOrder");