如何覆盖MigratorScriptingDecorator生成的SQL脚本

Dav*_*tte 6 entity-framework entity-framework-4

使用实体框架4.3.1代码优先和数据迁移.

我编写了一个实用程序,使用MigratorScriptingDecorator自动为目标数据库生成迁移脚本.

但是,有时从头开始重新生成目标数据库时,生成的脚本无效,因为它声明了两次具有相同名称的变量.

变量名是@ var0.

当应用多个迁移时,以及当至少两个迁移导致默认约束被删除时,似乎会发生这种情况.

生成脚本表单代码和使用程序包管理器控制台命令时都会出现此问题:

Update-Database -Script
Run Code Online (Sandbox Code Playgroud)

以下是生成的脚本中的违规片段:

DECLARE @var0 nvarchar(128)
SELECT @var0 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'SomeTableName')
Run Code Online (Sandbox Code Playgroud)

DECLARE @var0 nvarchar(128)
SELECT @var0 = name
FROM sys.default_constraints
WHERE parent_object_id = object_id(N'SomeOtherTableName')
Run Code Online (Sandbox Code Playgroud)

我希望能够覆盖为每次迁移生成SQL的点,然后添加"GO"语句,以便每次迁移都在一个单独的批处理中,这将解决问题.

任何人都有任何想法如何做到这一点,或者如果我咆哮错误的树然后你可以建议一个更好的方法?

Dav*_*tte 4

因此,通过广泛使用ILSpy和这个问题答案中的一些提示,我找到了一种方法。

详情如下,有兴趣的朋友可以看看。

问题

该类SqlServerMigrationSqlGenerator最终负责创建针对目标数据库执行的 SQL 语句,或者在使用-Script包管理器控制台中的开关或使用MigratorScriptingDecorator.

工作原理

SqlServerMigrationSqlGenerator检查which 中负责 a 的Genearate 方法DROP COLUMN,它看起来像这样:

protected virtual void Generate(DropColumnOperation dropColumnOperation)
{
    RuntimeFailureMethods
        .Requires(dropColumnOperation != null, null, "dropColumnOperation != null");
    using (IndentedTextWriter indentedTextWriter = 
        SqlServerMigrationSqlGenerator.Writer())
    {
        string value = "@var" + this._variableCounter++;
        indentedTextWriter.Write("DECLARE ");
        indentedTextWriter.Write(value);
        indentedTextWriter.WriteLine(" nvarchar(128)");
        indentedTextWriter.Write("SELECT ");
        indentedTextWriter.Write(value);
        indentedTextWriter.WriteLine(" = name");
        indentedTextWriter.WriteLine("FROM sys.default_constraints");
        indentedTextWriter.Write("WHERE parent_object_id = object_id(N'");
        indentedTextWriter.Write(dropColumnOperation.Table);
        indentedTextWriter.WriteLine("')");
        indentedTextWriter.Write("AND col_name(parent_object_id, 
                                                       parent_column_id) = '");
        indentedTextWriter.Write(dropColumnOperation.Name);
        indentedTextWriter.WriteLine("';");
        indentedTextWriter.Write("IF ");
        indentedTextWriter.Write(value);
        indentedTextWriter.WriteLine(" IS NOT NULL");
        indentedTextWriter.Indent++;
        indentedTextWriter.Write("EXECUTE('ALTER TABLE ");
        indentedTextWriter.Write(this.Name(dropColumnOperation.Table));
        indentedTextWriter.Write(" DROP CONSTRAINT ' + ");
        indentedTextWriter.Write(value);
        indentedTextWriter.WriteLine(")");
        indentedTextWriter.Indent--;
        indentedTextWriter.Write("ALTER TABLE ");
        indentedTextWriter.Write(this.Name(dropColumnOperation.Table));
        indentedTextWriter.Write(" DROP COLUMN ");
        indentedTextWriter.Write(this.Quote(dropColumnOperation.Name));
        this.Statement(indentedTextWriter);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以看到它跟踪使用的变量名称,但这似乎只在批处理(即单个迁移)内跟踪。因此,如果一个 migratin 包含多个DROP COLUM迁移,则上述方法可以正常工作,但如果有两个迁移导致DROP COLUMN生成,则该_variableCounter变量将被重置。

不生成脚本时不会遇到任何问题,因为每个语句都会立即针对数据库执行(我使用 SQL Profiler 检查)。

如果您生成 SQL 脚本并希望按原样运行它,尽管您遇到了问题。

解决方案

我创建了一个新的BatchSqlServerMigrationSqlGenerator继承,SqlServerMigrationSqlGenerator如下所示(注意您需要using System.Data.Entity.Migrations.Sql;):

public class BatchSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate
       (System.Data.Entity.Migrations.Model.DropColumnOperation dropColumnOperation)
    {
        base.Generate(dropColumnOperation);

        Statement("GO");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在要强制迁移使用自定义生成器,您有两个选择:

  1. 如果您希望将其集成到包管理器控制台中,请将以下行添加到您的Configuration类中:

       SetSqlGenerator("System.Data.SqlClient", 
                       new BatchSqlServerMigrationSqlGenerator());
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果您从代码生成脚本(就像我一样),请在代码中包含配置程序集的位置添加类似的代码行:

    migrationsConfiguration.SetSqlGenerator(DataProviderInvariantName, 
                       new BatchSqlServerMigrationSqlGenerator());
    
    Run Code Online (Sandbox Code Playgroud)