【问题标题】:EF migration for changing data type of columns用于更改列数据类型的 EF 迁移
【发布时间】: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 失败,因为一个或多个 对象访问此列。

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

【问题讨论】:

    标签: c# visual-studio entity-framework entity-framework-5 entity-framework-migrations


    【解决方案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

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

        【讨论】:

        • 这是个糟糕的主意。除非你的项目是全新的。不要听这个人的。
        • 我无法想象为什么每个人都想这样做
        猜你喜欢
        • 2015-02-07
        • 2019-07-14
        • 2017-07-28
        • 2020-09-03
        • 2016-02-29
        • 1970-01-01
        • 2013-01-28
        • 1970-01-01
        相关资源
        最近更新 更多