用于更改列数据类型的 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 迁移的主要内容,如果未能解决你的问题,请参考以下文章