我有一个使用OOB数据库初始化程序创建的数据库,我使用的是Code First和EF 4.3.1。
我想利用Add-Migration cmdlet上的新“IgnoreChanges”标志,以便我可以更改某些列并添加默认SQL值。基本上,我的一些实体有一个名为DateLastUpdated的列,我想将DEFAULT设置为sql表达式GETDATE()。
我使用“add-migration InitialMigration -ignorechanges”创建了InitialMigration,然后我将以下内容添加到Up()和Down():
public override void Up()
{
AlterColumn("CustomerLocations", "DateLastUpdated", c => c.DateTime(defaultValueSql: "GETDATE()"));
AlterColumn("UserReportTemplates", "DateLastUpdated", c => c.DateTime(defaultValueSql: "GETDATE()"));
AlterColumn("Chains", "DateLastUpdated", c => c.DateTime(defaultValueSql: "GETDATE()"));
}
public override void Down()
{
AlterColumn("CustomerLocations", "DateLastUpdated", c => c.DateTime());
AlterColumn("UserReportTemplates", "DateLastUpdated", c => c.DateTime());
AlterColumn("Chains", "DateLastUpdated", c => c.DateTime());
}
然后我尝试运行“Update-Database -verbose”,但我发现它正在尝试在数据库上创建相同的命名默认约束,并且SQL抛出异常:
Applying explicit migrations: [201203221856095_InitialMigration].
Applying explicit migration: 201203221856095_InitialMigration.
ALTER TABLE [CustomerLocations] ADD CONSTRAINT DF_DateLastUpdated DEFAULT GETDATE() FOR [DateLastUpdated]
ALTER TABLE [CustomerLocations] ALTER COLUMN [DateLastUpdated] [datetime]
ALTER TABLE [UserReportTemplates] ADD CONSTRAINT DF_DateLastUpdated DEFAULT GETDATE() FOR [DateLastUpdated]
System.Data.SqlClient.SqlException (0x80131904): There is already an object named 'DF_DateLastUpdated' in the database.
Could not create constraint. See previous errors.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async)
at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement)
at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.ExecuteStatements(IEnumerable`1 migrationStatements)
at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, XDocument targetModel, IEnumerable`1 operations, Boolean downgrading)
at System.Data.Entity.Migrations.DbMigrator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ApplyMigration(DbMigration migration, DbMigration lastMigration)
at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId)
at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration)
at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration)
at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore()
at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run()
There is already an object named 'DF_DateLastUpdated' in the database.
Could not create constraint. See previous errors.
看起来EF正在创建DEFAULT约束,方法是将“DF_”附加到列的名称,但不使用表的名称,以使其对表具有唯一性。这是一个已知的错误,还是我在这里做错了什么?
答案 0 :(得分:7)
这似乎是一个已知的错误:msdn forums
Andrew J Peters Microsoft(MSFT)回复:
感谢您报告此事。 RTM将解决该问题。
可能的解决方法是首先使列可以为空,即 将阻止迁移生成额外的DEFAULT约束。 创建列后,可以将其更改回 不可为空。
但它的定义并未在EF 4.3.1中修复。以下是来源的相关部分:
// Type: System.Data.Entity.Migrations.Sql.SqlServerMigrationSqlGenerator
// Assembly: EntityFramework, Version=4.3.1.0,
// Culture=neutral, PublicKeyToken=b77a5c561934e089
namespace System.Data.Entity.Migrations.Sql
{
public class SqlServerMigrationSqlGenerator : MigrationSqlGenerator
{
protected virtual void Generate(AlterColumnOperation alterColumnOperation)
{
//...
writer.Write("ALTER TABLE ");
writer.Write(this.Name(alterColumnOperation.Table));
writer.Write(" ADD CONSTRAINT DF_");
writer.Write(column.Name);
writer.Write(" DEFAULT ");
//...
因此EF不会尝试使约束名称唯一。
您应该尝试解决方法并将其报告为错误。
编辑:
我刚刚意识到上面提到的Generate
方法是virtual
所以在最坏的情况下你可以从SqlServerMigrationSqlGenerator
继承并修复SQL生成并将其设置为Configuration.cs中的sql生成器:
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient",
new MyFixedSqlServerMigrationSqlGenerator());
}
编辑2:
我认为最好的办法是直到它恢复到原始SQL:
public override void Up()
{
Sql(@"ALTER TABLE [CustomerLocations] ADD CONSTRAINT
DF_CustomerLocations_DateLastUpdated
DEFAULT GETDATE() FOR [DateLastUpdated]");
Sql(@"ALTER TABLE [CustomerLocations] ALTER COLUMN
[DateLastUpdated] [datetime]");
//...
}
答案 1 :(得分:7)
好的,基于nemesv的回答(已接受),这是我现在最终解决问题的方法,直到修正正式发布:
internal class MyFixedSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AlterColumnOperation alterColumnOperation)
{
if (alterColumnOperation == null)
throw new ApplicationException("alterColumnOperation != null");
ColumnModel column = alterColumnOperation.Column;
if ((column.DefaultValue != null) || !string.IsNullOrWhiteSpace(column.DefaultValueSql))
{
using (IndentedTextWriter writer = Writer())
{
writer.Write("ALTER TABLE ");
writer.Write(this.Name(alterColumnOperation.Table));
writer.Write(" ADD CONSTRAINT DF_");
writer.Write(alterColumnOperation.Table + "_"); // <== THIS IS THE LINE THAT FIXES THE PROBLEM
writer.Write(column.Name);
writer.Write(" DEFAULT ");
writer.Write(column.DefaultValue != null ? base.Generate(column.DefaultValue) : column.DefaultValueSql);
writer.Write(" FOR ");
writer.Write(this.Quote(column.Name));
this.Statement(writer);
}
}
using (IndentedTextWriter writer2 = Writer())
{
writer2.Write("ALTER TABLE ");
writer2.Write(this.Name(alterColumnOperation.Table));
writer2.Write(" ALTER COLUMN ");
writer2.Write(this.Quote(column.Name));
writer2.Write(" ");
writer2.Write(this.BuildColumnType(column));
if (column.IsNullable.HasValue && !column.IsNullable.Value)
{
writer2.Write(" NOT NULL");
}
this.Statement(writer2);
}
}
}
internal sealed class Configuration : DbMigrationsConfiguration<MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new MyFixedSqlServerMigrationSqlGenerator());
}
...
}
答案 2 :(得分:1)
此解决方案在EF 6.1.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 ());
}
...
}