Entity Framework 6: 如果存在则删除列的迁移

8

在删除列的迁移操作期间,如何生成SQL以先检查该列是否存在,然后再尝试删除它?

对于删除列操作,Entity Framework当前会生成类似于以下SQL:

// Migration Operation:
DropColumn("dbo.Table", "Column");

// TSQL generated:
// Dependency management logic ...
ALTER TABLE [dbo].[Table] DROP COLUMN [Column]

如何修改SQL以先检查列是否存在:
// Migration Operation:
DropColumn("dbo.Table", "Column");

// TSQL desired:
IF EXISTS (SELECT * FROM sys.columns WHERE object_id = Object_id('dbo.Table') AND name = 'Column')
BEGIN
    // Dependency management logic ...
    ALTER TABLE [dbo].[Table] DROP COLUMN [Column]
END

我知道可以通过继承SqlServerMigrationSqlGenerator来自定义迁移SQL。我的尝试未能将默认的删除列逻辑包装在一个IF块中。请参见以下示例:

public class CustomSqlServerMigrationSqlGenerator: SqlServerMigrationSqlGenerator
{
    /// <summary>
    /// Drop column only if it exists.
    /// </summary>
    protected override void Generate(System.Data.Entity.Migrations.Model.DropColumnOperation dropColumnOperation)
    {
        using (var writer = Writer())
        {
            writer.WriteLine(
              "IF EXISTS (SELECT * FROM sys.columns WHERE object_id = Object_id('{0}') AND name = '{1}')",
              dropColumnOperation.Table,
              dropColumnOperation.Name);
            writer.WriteLine("BEGIN");
            Statement(writer);
        }

        // Default drop column logic
        base.Generate(dropColumnOperation);

        using (var writer = Writer())
        {
            writer.WriteLine("END");
            Statement(writer);
        }
    }
}

参考资料:


1
除非您的迁移出现问题,否则通常不需要执行此操作。EF会从最后一个迁移到当前迁移执行模式比较。如果这是SQL Server 2016,则可以将新的Drop If Exists语句编码为Sql()命令。 - Steve Greene
@SteveGreene 它被用于在团队环境中管理迁移。当有更改时,我们使用相同的迁移并重新设置脚手架。它的用例是简化开发中的重新设置脚手架。一旦迁移到达生产,它就不会变动。这个问题集中在自定义SQL迁移生成上,而不是手动添加迁移操作,比如Sql()。当然还有其他管理团队迁移的方法,但这些方法不适合我们的需求:https://msdn.microsoft.com/en-us/library/dn481501(v=vs.113).aspx。 - Hans Vonn
在第二篇文章中,使用了protected override void Generate(MigrationOperation migrationOperation)方法,而不是具有相同名称但带有DropColumnOperation参数的方法:if (operation as DropColumnOperation) - Slava Utesinov
@SteveGreene 你说得没错,但是使用 Code First From Database 从现有数据库开始时,很容易搞砸他的数据库或者出现错误的命名约定。EF 也远非完美。EF 无法处理所有现有数据库的情况。 - Bastien Vandamme
1个回答

2
如果您已经正确配置了您的 CustomSqlServerMigrationSqlGenerator,那么在执行 Update-Database 时,您应该会遇到这个错误信息:
System.Data.SqlClient.SqlException (0x80131904): Incorrect syntax near 'BEGIN'.

问题在于您已经构造并执行了3个部分语句,调用Statement()默认作为单个批处理操作执行,直到实际的DropColumn语句和End被包含在语句中,才算是有效的语法。
由于基本实现不允许我们通过文本编写器(我们需要的方法标记为protected)进行传递,因此我们被迫完全忽略基本实现。
/// <summary>
/// Drop column only if it exists.
/// </summary>
/// <remarks>This helps when we're stuffed up a previous migration or someone has already executed the drop in the DB direct.</remarks>
protected override void Generate(System.Data.Entity.Migrations.Model.DropColumnOperation dropColumnOperation)
{
    using (var writer = Writer())
    {
        writer.WriteLine(
          "IF EXISTS (SELECT * FROM sys.columns WHERE object_id = Object_id('{0}') AND name = '{1}')",
          dropColumnOperation.Table,
          dropColumnOperation.Name);
        writer.WriteLine("BEGIN");

        // Base Implementation, increase the indentation
        writer.Indent++;
        writer.WriteLine("ALTER TABLE {0}", Quote(dropColumnOperation.Table));
        writer.WriteLine("DROP COLUMN {0}", Quote(dropColumnOperation.Name));
        writer.Indent--;

        writer.WriteLine("END");

        Statement(writer);
    }
}

如果您没有看到当前的错误,可能是因为CustomSqlServerMigrationSqlGenerator没有正确注册,请确保您已经在Configuration.cs文件中的Configuration类的构造函数中设置了它,例如:

public Configuration()
{
    AutomaticMigrationsEnabled = false;
    AutomaticMigrationDataLossAllowed = false;
    // Register the Customized SQL Generator to use
    this.SetSqlGenerator("System.Data.SqlClient", new CustomSqlServerMigrationSqlGenerator());
}

如果您需要调试此过程,请遵循此帖子中的建议,该帖子与此情景类似,您可以在生成器类的构造函数中设置断点:

public class CustomSqlServerMigrationSqlGenerator: SqlServerMigrationSqlGenerator
{
    public CustomSqlServerMigrationSqlGenerator()
        : base()
    {
        if (!System.Diagnostics.Debugger.IsAttached)
            System.Diagnostics.Debugger.Launch();
    }

    ...
}

你会如何处理需要先删除的依赖项? - Hans Vonn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接