【问题标题】:How to run migration SQL script using Entity Framework Core如何使用 Entity Framework Core 运行迁移 SQL 脚本
【发布时间】:2017-07-11 13:18:44
【问题描述】:

我遇到了一个问题,我无法访问 SQL 脚本来应用迁移。 这是我的迁移代码:

 public partial class AddSomethingMigration : Migration
{
    private const string MIGRATION_SQL_SCRIPT_FILE_NAME = @"Migrations\Scripts\20170710123314_AddSomethingMigration.sql";

    protected override void Up(MigrationBuilder migrationBuilder)
    {
        string sql = Path.Combine(Directory.GetParent(Directory.GetCurrentDirectory()).FullName, MIGRATION_SQL_SCRIPT_FILE_NAME));
        migrationBuilder.Sql(File.ReadAllText(sql));
    }
}

因此,当我在本地计算机上使用包管理器控制台时,一切正常。但是当我部署到环境时,我发现文件存在差异。

我可以通过 EF 迁移自动运行我的静态 SQL 脚本,还是应该将 SQL 查询内联粘贴到代码中?

【问题讨论】:

标签: c# asp.net-core entity-framework-core entity-framework-migrations


【解决方案1】:

我找到了这个问题的几个答案。

  1. 将脚本添加为项目资源并像这样使用它:

    string sql = Resources._20170630085940_AddMigration;
    migrationBuilder.Sql(sql);
    

这个选项不太好,因为 .sql 会嵌入到程序集中。

  1. 如果使用.csproj结构的Net Core项目,可以在xml中添加itemgroup:

    <ItemGroup> <Content Include="Migrations\**\*.sql" CopyToPublishDirectory="PreserveNewest" /><!-- CopyToPublishDirectory = { Always, PreserveNewest, Never } --></ItemGroup>
    

然后指定文件的路径如:

Path.Combine(AppContext.BaseDirectory, relativePath)

【讨论】:

  • 代码有什么用?
  • 这些是 CopyToPublishDirectory 的选项吗?
  • @shkapo,如果我理解正确,方法 1 是将 SQL 查询放入 resx 文件中。首先,我不喜欢这样,因为 .resx 不容易编辑:我更喜欢在 Visual Studio 中有一个 .sql 文件。其次,我不明白您为什么说将 SQL 嵌入到程序集中是不好的。我不喜欢依赖于 SQL 文件的程序集,你能详细说明一下吗?查看我提交的答案,让我知道您的想法。
  • @Yanal-YvesFargialla 您将文件作为资源添加到项目中。所以它仍然在它自己的 .sql 文件中,然后你将该文件添加为资源。
【解决方案2】:

我喜欢做的是将 SQL 脚本作为资源嵌入到程序集中,以便程序集不依赖任何外部文件。我已经使用 Visual Studio Community 2019 16.4.2 测试了这种方法。 在我的情况下,DbContext 保存在 .NET Standard 2.0 库中,而我的 Web 应用程序正在运行 .NET Core 2.2。

首先你需要创建一个迁移文件:

  1. 在 Visual Studio 中,确保将 Web 应用程序设置为启动项目。
  2. 在 Visual Studio 中打开 PMC:查看 -> 其他窗口 -> 包管理器控制台 (PMC)
  3. 在 PMC 中将默认项目设置为包含 DbContext 的项目(在我的例子中是 .NET 标准 2.2 库)
  4. 添加新的迁移:

    Add-Migration RunSqlScript

在迁移文件夹中添加一个Sql Script(为方便起见,我将其命名为与迁移文件相同的前缀)

在文件属性窗口中确保构建操作是“嵌入式资源” 请注意,我们不需要复制到输出文件夹,因为 sql 脚本将嵌入到程序集中。

更新RunSqlScript迁移中的Up方法

var assembly = Assembly.GetExecutingAssembly();
string resourceName = typeof(RunSqlScript).Namespace + ".20191220105024_RunSqlScript.sql";
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
  using (StreamReader reader = new StreamReader(stream))
  {
    string sqlResult = reader.ReadToEnd();
    migrationBuilder.Sql(sqlResult);
  }
}

在我的应用程序中,我已将此代码重新分解为实用方法。为简洁起见,我在没有这种重构的情况下发布了。

更新:

我上面提到的重构代码:

public static class MigrationUtility
{
  /// <summary>
  /// Read a SQL script that is embedded into a resource.
  /// </summary>
  /// <param name="migrationType">The migration type the SQL file script is attached to.</param>
  /// <param name="sqlFileName">The embedded SQL file name.</param>
  /// <returns>The content of the SQL file.</returns>
  public static string ReadSql(Type migrationType, string sqlFileName)
  {
    var assembly = migrationType.Assembly;
    string resourceName = $"{migrationType.Namespace}.{sqlFileName}";
    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
    {
      if (stream == null)
      {
        throw new FileNotFoundException("Unable to find the SQL file from an embedded resource", resourceName);
      }

      using (var reader = new StreamReader(stream))
      {
        string content = reader.ReadToEnd();
        return content;
      }
    }
  }
}

使用示例:

string sql = MigrationUtility.ReadSql(typeof(RunSqlScript), "20191220105024_RunSqlScript.sql");
migrationBuilder.Sql(sql);

【讨论】:

  • 感谢这个例子。但是,您的 sn-p 并未反映您的屏幕截图: string resourceName = typeof(RunSqlScript).Namespace + ".20191220105024_RunSqlScript.sql";
  • 好消息@Umar3x !我最初回答了这个 SO:stackoverflow.com/questions/32125937/… 用于 EF 4。当我回答 EF 核心的等效问题时,我已经在 VS 2019 16.4.2 上对其进行了测试,然后再发布并复制/粘贴实际代码。但是,我很懒,我重用了我最初答案的屏幕截图。
  • 我真的很喜欢这种方法,我想看看你重新分解的方法。
  • @c0y0teX,我已经用重新分解的代码更新了我的 anwser。我希望这会有所帮助。
  • 感谢更新。
【解决方案3】:

这是使用 EmbeddedResource 的方法的升级。主要思想是使用抽象类和一个与migration同名的sql文件。

public abstract class SqlMigration : Migration
{
    protected sealed override void Up(MigrationBuilder migrationBuilder)
    {
        var assembly = Assembly.GetExecutingAssembly();
        var type = GetType();
        var regex = new Regex($@"{Regex.Escape(type.Namespace)}\.\d{{14}}_{Regex.Escape(type.Name)}\.sql");

        var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(x => regex.IsMatch(x));
        using var stream = assembly.GetManifestResourceStream(resourceName);
        using var reader = new StreamReader(stream);
        var sqlResult = reader.ReadToEnd();
        migrationBuilder.Sql(sqlResult);
    }
}

它只是使用真实类型的名称和命名空间进行正则表达式。 继承类将如下所示:

public partial class RunSqlScript : SqlMigration
{
    protected override void Down(MigrationBuilder migrationBuilder)
    {
        // Down code here
    }
}

项目将类似于:

.

【讨论】:

    【解决方案4】:

    我根据4lexKislitsyn 的回答创建了一个扩展方法。这假定您的文件以 .sql 扩展名结尾,并且它是您运行迁移的任何项目中的嵌入式资源。您当然可以将 .sql 部分移至迁移的 Up,但这对我来说似乎更干净。

    public static class MigrationExtensions
    {
        public static void RunSqlScript(this MigrationBuilder migrationBuilder, string script)
        {
            var assembly = Assembly.GetExecutingAssembly();
            var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(x => x.EndsWith($"{script}.sql"));
            using var stream = assembly.GetManifestResourceStream(resourceName);
            using var reader = new StreamReader(stream);
            var sqlResult = reader.ReadToEnd();
            migrationBuilder.Sql(sqlResult);
        }
    }
    

    用作

    public partial class AddViews : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.RunSqlScript("nameOfMyFile");
        }
    
        protected override void Down(MigrationBuilder migrationBuilder)
        {
    
        }
    }
    

    【讨论】:

      【解决方案5】:

      我发现从提供的 MigrationAttribute 评估 sql 文件名是最好的方法。

        public class EmbeddedSqlFileMigration : Migration
          {
              protected override void Up(MigrationBuilder migrationBuilder)
              {
                  var assembly = Assembly.GetExecutingAssembly();
                  var type = GetType();
                  var migrationAttribute = type.GetCustomAttribute<MigrationAttribute>();
                  if (migrationAttribute == null)
                      throw new InvalidOperationException("A migration requires a MigrationAttribute.");
      
                  var sqlResourceFilename = $"{type.Namespace}.{migrationAttribute.Id}.sql";
                  var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(r => r == sqlResourceFilename);
                  if (resourceName == null)
                  {
                      throw new FileNotFoundException(
                          $"Embedded resource '{sqlResourceFilename}' was not found in assembly '{assembly.FullName}'.");
                  }
      
                  using var stream = assembly.GetManifestResourceStream(resourceName);
                  if (stream == null)
                  {
                      throw new InvalidOperationException(
                          $"Unable to get stream for embedded resource '{sqlResourceFilename}' in assembly '{assembly.FullName}'.");
                  }
      
                  using var reader = new StreamReader(stream);
                  var sqlResult = reader.ReadToEnd();
                  migrationBuilder.Sql(sqlResult);
              }
          }
      

      【讨论】:

        【解决方案6】:

        我的技术主管和我试图弄清楚为什么我还没有找到插入语句的简单答案。双方都没有对对方感到沮丧,他们都对我们遇到的缺乏简单性感到沮丧。

        我们发现:

        1. https://www.learnentityframeworkcore.com/raw-sql
        2. https://mycodingtips.com/2021/9/20/how-to-run-sql-scripts-in-a-file-using-ef-core-migrations 3.https://www.codeproject.com/Articles/1173837/BulkInsert-with-the-Entity-Framework
        3. https://www.yogihosting.com/insert-records-entity-framework-core/

        一切都有效还是一个好的开始,没有这么简单:

        1. 生成所需的插入语句
        2. 在每个之间使用 GO 分隔符
        3. 在控制台项目中解析文件,最多计算 500 个 Go 分隔符以生成 500 个语句块
        4. 运行每个块,为每个块创建一个字符串并使用 DbSet.FromRawSQL(插入块)

        我将编写方法并在完成后发布代码。

        【讨论】:

          猜你喜欢
          • 2017-04-26
          • 1970-01-01
          • 1970-01-01
          • 2019-09-02
          • 1970-01-01
          • 2017-10-27
          • 2021-01-21
          • 2018-11-05
          • 2020-03-09
          相关资源
          最近更新 更多