用于更改列数据类型的 EF 迁移

Posted

技术标签:

【中文标题】用于更改列数据类型的 EF 迁移【英文标题】:EF migration for changing data type of columns 【发布时间】:2013-07-27 12:33:48 【问题描述】:

我的项目中有一个模型如下:

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; 

迁移如下

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

当我使用更新数据库时,会出现以下错误:

对象 'DF__Receiv__FromN__25869641' 依赖于列 '从否'。 ALTER TABLE ALTER COLUMN FromNo 失败,因为一个或多个 对象访问此列。

这个表没有外键或者其他什么问题?

【问题讨论】:

【参考方案1】:

您的列上有一个默认约束。您需要先删除约束,然后更改您的列。

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

您可能还必须删除其他列上的默认约束。

我刚刚看到安德烈的评论(我知道 - 很晚了),他是正确的。所以更稳健的方法是使用类似的东西:

 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)

我知道这可能对 OP 没有帮助,但希望它可以帮助遇到此问题的其他人。

【讨论】:

约束名称由 SQL Server 自动生成。虽然此代码将在开发环境中工作 - 它不会在生产环境中,因为在不同的数据库中,约束名称会不同 非常好的方法。特别是第二个。完美运行。 @Bigfellahull 很抱歉是个笨蛋——你会把答案的第二个版本放在哪里? @DaveGordon:你可以把它放在Sql()方法的调用中,像这样:Sql(@"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)"); 该查询在删除 DF 时效果很好,但在我的情况下,将 DateTime 更改为 TimeSpan 字段,这导致了一个新错误 - 对象 'DF_dbo.Entity_Column' 依赖于列 'Column'。是否有查询也可以删除它?【参考方案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());

【讨论】:

这很完美,比公认的答案更好,因为它是动态的而不是硬编码的。 我试过这个并得到错误:找不到任何适合指定文化或中性文化的资源。确保“XXXX2.DAL.Migrations.ChangeProcessedToByte.resources”在编译时被正确嵌入或链接到程序集“XXXX2.DAL”中,或者所有需要的附属程序集都是可加载的并且完全签名。 @Kleky 我遇到了这个问题,并通过将 MigrationExtensions 类放在与迁移不同的文件中来解决它(在我的情况下,MigrationExtensions.cs 位于 Migrations 文件夹中)。 此解决方案缺少Down() 方法。【参考方案3】:

更好的方法是永远解决问题。

您可以从 System.Data.Entity.SqlServer 命名空间中实现从 SqlServerMigrationSqlGenerator 派生的自定义 sql 生成器类:

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

并设置此配置:

internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>

    public Configuration()
    
        AutomaticMigrationsEnabled = true;

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

【讨论】:

【参考方案4】:

这是将现有列更改为已具有外键约束的“非空”的示例。 该列的名称是表“SubTable”中的“FKColumnName”,它引用了表“MainTable”中的“Id”列。

上传脚本:

将列设为“不可为空”后,首先删除索引和外键,然后重新创建。

下载脚本:

这里的步骤是相同的​​,只是该列再次可以为空。

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

【讨论】:

这对我有用,只是我将索引的 new[] "FKColumnName" 更改为 "IX_ColumnName",因为这是我数据库中的名称。【参考方案5】:

我遇到了这个问题,整数列的默认值为零约束。

就我而言,我通过从 Entity Framework 6.1.x 切换到 EF 6.2.0 解决了这个问题。

在 6.2 之前的 EF 中存在一个已知错误,这意味着 EF 在更改列时有时不会自动处理这些类型的约束。该错误在official EF github repo here 上进行了描述,Bricelam 将问题描述为:

当添加 NOT NULL 列时,我们会为任意列合成一个默认值 现有的行。看起来我们的逻辑是删除默认约束 在 ALTER COLUMN 没有考虑到这一点之前。

该问题的修复提交can be found here。

【讨论】:

【参考方案6】:

如果您使用的是 EF:

删除迁移文件夹和数据库 enable-migrations add-migration initial update-database

虽然,此解决方案将删除数据库中的所有当前项目。如果这不是您的意图,我会建议其他答案之一。

【讨论】:

这是个糟糕的主意。除非你的项目是全新的。不要听这个人的。 我无法想象为什么每个人都想这样做

以上是关于用于更改列数据类型的 EF 迁移的主要内容,如果未能解决你的问题,请参考以下文章

EF5代码优先 - 使用迁移更改列类型

更改列的 Rails 迁移

在 Laravel 迁移中更改列类型的最佳方法是啥?

“从数据库更新模型”看不到类型更改

你如何更改rails中的列数据类型?

Oracle 从 varray 更改列数据类型