5

我有一個使用OOB數據庫初始化程序創建的數據庫,並且我在EF 4.3.1中使用Code First。EF 4.3.1遷移異常 - AlterColumn defaultValueSql爲不同的表創建相同的默認約束名稱

我想利用Add-Migration cmdlet上新的「IgnoreChanges」標誌,以便可以更改一些列並添加默認SQL值。實質上,我的一些實體有一個名爲DateLastUpdated的列,我想將DEFAULT設置爲sql表達式GETDATE()。

我創建了InitialMigration使用「添加遷移InitialMigration -ignorechanges」,然後添加以下到向上()和向下():

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

然後我試圖運行「更新 - 數據庫 - 詳細」,但我看到它正試圖創建數據庫在同一個名爲default的約束,以及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

檢查Elyas答案。這是一個很好的解決方案 – Morteza 2017-02-19 07:19:47

回答

7

看來這是一個已知的bug:msdn forums

安德魯·彼得斯Ĵ微軟(MSFT)的回答是:

感謝您報告這一點。 RTM將解決該問題。

可能的解決方法是首先使列可以爲空,即 將阻止Migrations生成額外的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]"); 
    //... 
} 
+0

你從哪裏得到EF的代碼?我使用的是Reflector,但是這個Generate方法的一大塊可能使用了很多lambda表達式,funcs/actions和匿名的東西,所以Reflector並沒有給我一個好的版本來複制和修改。 – 2012-03-23 15:27:32

+0

我在Resharper中使用了內置的反編譯器(可以使用免費的Jetbrains反編譯器[dotPeek](http://www.jetbrains.com/decompiler/index.html?topDP)),Telerik的JustDecompile也生成了幾乎相同的source ..但是都包含很多lambads :(所以它不能直接重用) – nemesv 2012-03-23 16:10:51

+0

@ThiagoSilva我認爲最簡單的方法就是在原始SQL中編寫這種遷移,直到修正這個錯誤,我已經更新了我的答案 – nemesv 2012-03-24 19:04:13

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()); 
    } 
    ... 
} 
+1

我跑了進入一個問題,其中'alterColumnOperation.Table'包含模式,導致約束的名稱無效(如'DF_dbo.MyTable_MyColumn')。它用這個幫助方法:'私人字符串RemoveSchema(字符串名稱){返回名稱.Split('。')。Last(); }' – 2012-08-01 20:22:05

+0

不知道它是否更安全,但如果它們改變默認的EF方法,而不是第二個'使用IndentedTextWriter ...'塊,可以將默認值設置爲'null'並讓基方法完成創建子句: 'column.DefaultValue = null; column.DefaultValueSql = null; base.Generate(alterColumnOperation);' – drzaus 2012-08-17 16:58:05

+1

在EF 6.0.0.3中仍未修復。這很蹩腳...... – Liviu 2013-05-29 20:39:01

1

該解決方案在EF 6.1.3測試。最有可能在以前的版本上工作。

您可以實現從SqlServerMigrationSqlGenerator從System.Data.Entity.SqlServer命名空間派生的自定義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()); 
    } 
    ... 
} 
相關問題