有没有办法从已发布的 DLL 中获取 运行 EF Core RC2 工具?

Is there a way to run EF Core RC2 tools from published DLL?

发布 .Net Core RC1 应用程序后,project.json 中指定的命令已为其创建相应的 .cmd 文件,可以在部署后执行(例如 web.cmd 和 ef.cmd ).在我的例子中,我想在我的部署目标上 运行 以下 Entity Framework 命令:

dotnet ef database update -c MyContext

当我从包含源代码的文件夹中 运行 时,这工作正常,但是在发布之后,它似乎没有在编译的 DLL 中找到命令。我对 RC2 命令变化的理解是 'tools' 可以编译为名为 dotnet-*.dll 的独立应用程序,并且可以通过 CLI 执行。 Entity Framework 核心工具如何在发布的输出中作为可执行 DLL 公开?

仅供参考,我的 build/deployment 工作流程如下:

TeamCity

dotnet restore => dotnet build => dotnet test => dotnet publish

章鱼部署

上传包 => EF 更新数据库 => 等

我在一个项目中遇到了同样的问题,但出于多种原因,我不希望在应用程序启动时自动迁移到 运行。

为了解决这个问题,我更新了 Program.cs 以接受两个参数(下面列出了完整代码)

  • --ef-migrate,应用所有挂起的迁移,并且
  • --ef-migrate-check,以验证是否已应用所有迁移

如果存在参数,则应用 EF 操作并退出程序,否则启动 Web 应用程序。

请注意,它依赖于 Microsoft.Extensions.CommandLineUtils 包来简化命令行解析。

对于章鱼部署,然后可以将包发布两次到不同的位置 - 一次用于 运行ning 迁移,另一次用于虚拟主机。在我们的例子中,我们添加了一个“post 部署 powershell 脚本”,内容为

$env:ASPNETCORE_ENVIRONMENT="#{Octopus.Environment.Name}"
dotnet example-app.dll --ef-migrate

在 docker 上下文中 它也可以完美工作

docker run -it "example-app-container" dotnet example-app.dll --ef-migrate

完全 Program.cs 不包括命名空间和使用:

//Remember to run: dotnet add package Microsoft.Extensions.CommandLineUtils
public class Program
{
    public static void Main(string[] args)
    {
        var commandLineApplication = new CommandLineApplication(false);
        var doMigrate = commandLineApplication.Option(
            "--ef-migrate",
            "Apply entity framework migrations and exit",
            CommandOptionType.NoValue);
        var verifyMigrate = commandLineApplication.Option(
            "--ef-migrate-check",
            "Check the status of entity framework migrations",
            CommandOptionType.NoValue);
        commandLineApplication.HelpOption("-? | -h | --help");
        commandLineApplication.OnExecute(() =>
        {
            ExecuteApp(args, doMigrate, verifyMigrate);
            return 0;
        });
        commandLineApplication.Execute(args);
    }

    private static void ExecuteApp(string[] args, CommandOption doMigrate, CommandOption verifyMigrate)
    {
        Console.WriteLine("Loading web host");
        //
        // Please note that this webHostBuilder below is from an older 
        // dotnet core version. Newer dotnet cores have a simplified version
        // Use that instead and just take the command line parsing stuff with you
        var webHost = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        if (verifyMigrate.HasValue() && doMigrate.HasValue())
        {
            Console.WriteLine("ef-migrate and ef-migrate-check are mutually exclusive, select one, and try again");
            Environment.Exit(2);
        }

        if (verifyMigrate.HasValue())
        {
            Console.WriteLine("Validating status of Entity Framework migrations");
            using (var serviceScope = webHost.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
            { 
                using (var context = serviceScope.ServiceProvider.GetService<DatabaseContext>())
                {
                    var pendingMigrations = context.Database.GetPendingMigrations();
                    var migrations = pendingMigrations as IList<string> ?? pendingMigrations.ToList();
                    if (!migrations.Any())
                    {
                        Console.WriteLine("No pending migratons");
                        Environment.Exit(0);
                    }

                    Console.WriteLine("Pending migratons {0}", migrations.Count());
                    foreach (var migration in migrations)
                    {
                        Console.WriteLine($"\t{migration}");
                    }

                    Environment.Exit(3);
                }
            }
        }

        if (doMigrate.HasValue())
        {
            Console.WriteLine("Applyting Entity Framework migrations");
            using (var serviceScope = webHost.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetService<DatabaseContext>())
                {
                    context.Database.Migrate();
                    Console.WriteLine("All done, closing app");
                    Environment.Exit(0);
                }
            }
        }

        // no flags provided, so just run the webhost
        webHost.Run();
    }
}

有一个非常有用的 post 解决了这个问题 here

它对我有用(我不得不稍微调整一下命令,但它为我提供了良好的入门基础)。

总之:您可以通过传递 ef.dll 来复制 dotnet ef database update 命令(例如,直接从您的 nuget 文件夹(或者如果您没有 nuget,则从其他地方复制,因为您在一台生产机器..)),你的 .dll 包含带有一些附加参数(见下文)到 dotnet.exe(或 linux 等价物)的迁移。

为完整起见,这里是 .cmd(也来自博客post!)

set EfMigrationsNamespace=%1
set EfMigrationsDllName=%1.dll
set EfMigrationsDllDepsJson=%1.deps.json
set DllDir=%cd%
set PathToNuGetPackages=%USERPROFILE%\.nuget\packages
set PathToEfDll=%PathToNuGetPackages%\microsoft.entityframeworkcore.tools.dotnet.0.0\tools\netcoreapp1.0\ef.dll

dotnet exec --depsfile .\%EfMigrationsDllDepsJson% --additionalprobingpath %PathToNuGetPackages% %PathToEfDll% database update --assembly .\%EfMigrationsDllName% --startup-assembly .\%EfMigrationsDllName% --project-dir . --content-root %DllDir% --data-dir %DllDir% --verbose --root-namespace %EfMigrationsNamespace%

(一个bash版本如果这个cmd在博客里post)

顺便说一句。许多 github 问题中也提到了这种方法:https://github.com/aspnet/EntityFramework.Docs/issues/328 https://github.com/aspnet/EntityFramework.Docs/issues/180

ps:我在 blog of Ben Day 中找到了这个,所以所有功劳归于 Ben!

不幸的是,EF Core 迁移很糟糕,很多... 我已经看到了很多解决方案,但让我们列一个清单。因此,这里是您可以对 运行 执行的操作,并在没有 Visual Studio 的情况下部署 EF 迁移。下面的None是完美的解决方案都有一些注意事项:

  1. 使用 EF Core Tools here 是一个 link 官方 MS 站点,其中解释了如何安装和使用它。
  • 优点:微软官方工具。所有版本的 .NET Core 都支持。
  • 缺点:好像是EF6“Migrate.exe”的继承者。 但事实并非如此!目前,如果没有实际的源代码 (.csproj),则无法使用此工具。这不太适合 Live/Prod 部署。通常您的数据库服务器上没有 C# 项目。
  1. dotnet exec 我试图理解大量记录不完整的参数。并且未能 运行 迁移,直到找到 this script。这个名字暗示了 .NET 核心 2.1,但我已经将它与 3.0 一起使用并且工作了。 更新:没有 运行 它与 .NET 5
  • 优点:可以像EF6“migrate.exe”一样使用。最后迁移工作没有源代码。这很可能是从脚本和使用程序集进行迁移的唯一方法。
  • 缺点: 设置脚本非常困难,容易遗漏一个参数。没有真正记录解决方案,可能会将 .NET Core 版本更改为版本。此外,您很可能还需要更改代码。您必须实现 IDesignTimeDbContextFactory<DbContext> 接口才能使其工作。还要确保您的部署服务器上有 EF.dllMicrosoft.EntityFrameworkCore.Design.dll。 linked 脚本正在许多文件夹中寻找那些。最好是在构建期间将其从您的 .nuget 文件夹复制到您的工件。听起来很复杂,是的......但是 linked 脚本很有帮助。
  1. EF 迁移添加到您的 Startup.cs 或代码开始 运行ning 并有权访问 DBContext 的任何位置。使用 dbContext.Database.Migrate();
  • 优点:迁移每次都会自动发生,无需执行任何其他操作。
  • 缺点:迁移每次都会自动发生...您可能不希望发生的问题。 它还会在每次应用程序启动时 运行。所以你的启动时间会很糟糕。
  1. Custom app. 与前面的解决方案(第3点)类似。因此,您使用 .NET 代码来 运行 迁移。但与其将其放入您的应用程序,不如创建一个小型控制台应用程序 并在其中调用 migrate。您必须构建此应用程序并在部署期间将其放入工件 运行。
  • 优点:不涉及脚本。您可以随时在部署管道中调用它。所以你真正的应用程序启动时间不会受到影响。
  • 缺点:您必须维护、构建和打包应用程序才能执行 EF Core 迁移。
  1. 如果您使用 Azure Devops 进行部署,则可以使用 extension like this。或者只搜索 Azure Devops Marketplace。
  • 优点:它应该可以工作 :) 没有尝试过它们中的任何一个,也不知道它们的作用。 (我很确定他们也在使用 'dotnet exec' 第 2 点。)
  • 缺点:并非每个人都可以从 Azure Devops 访问 Live/Prod。
  1. 生成SQL脚本:如果none以上的工作你可以生成迁移SQL和运行它之后。 运行 带有“脚本”参数的 EF 工具:dotnet ef migrations script --output <pathAndFile>.sql --context <DbContextName> --idempotent。输出是一个 SQL 文件,可以手动执行或通过 CI/CD 管道中的脚本执行。
  • 优点:如果您没有访问或模式更改权限来生产仅生产数据库的 DBA,那么这是一个完美的解决方案。您可以向 DBA 提供“安全可靠”的 SQL 文件 运行...
  • 缺点:强调此解决方案非常重要必须运行在您的.csproj文件所在的工作目录中。所以它需要源代码! 此外,您还必须稍微更改一下代码。需要执行 IDesignTimeDbContextFactory<DbContext>.

更新:.NET 5 中有一些改进。现在更容易实施和使用 IDesignTimeDbContextFactory but most importantly Microsoft fixed this bug。现在可以将 SQL 连接字符串作为 args 传递。因此,如果您实现了 IDesignTimeDbContextFactory<T>,那么将它与 .NET CLI 和 EF 工具一起使用会很简单:

dotnet ef database update --context <DbContextName> --project "**/<ProjectName>.csproj" -- "<SQL connection will be passed into args[0]>"

同样重要的是要强调这仅适用于 .NET 5,并且还需要源代码!您还可以将它与选项 6 一起使用(生成 SQL 脚本) .

第二个烦人的问题一旦实现 IDesignTimeDbContextFactory<T> 这将被所有 ef 命令发现(甚至在开发期间来自 Visual Studio 的命令 运行)。 如果您需要来自 args[0] 的 SQL 连接字符串,您必须在开发期间将其传入 migrations add 或任何其他 ef 命令!

抱歉,列表太长了。但希望它能有所帮助。

在我的上下文中,我从

那里得到了这个 hack
    // Hack added so EntityFrameworkCore\Add-Migration initial works
public class ApplicationContextDbFactory : IDesignTimeDbContextFactory<MyContext>
{
    MyContext IDesignTimeDbContextFactory<MyContext>.CreateDbContext(string[] args)
    {
        IConfigurationRoot configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json")
            .Build();

        var optionsBuilder = new DbContextOptionsBuilder<MyContext>();
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("StreamCheckpointContextDB"));
        return new MyContext(optionsBuilder.Options);
    }
}

我 Program.Main 读起来是这样的...

    public static void Main(string[] args)
    {
        if (args.Contains("JustMigrateMe"))
        {
            IDesignTimeDbContextFactory<MyContext> factory = new ApplicationContextDbFactory();
            var ctx = factory.CreateDbContext(new string[0]);
            ctx.Database.Migrate();
            ctx.Dispose();
            return;
        }

        // Other stuff
    }
}

因此,为了应用迁移,我只需使用添加的参数调用 exe。

对于 EF Core 3.1,我在发布文件文件夹中使用以下行 运行 成功。当然可以使用

调整 MyUser 的路径

dotnet exec --depsfile ProjectName.deps.json --runtimeconfig ProjectName.runtimeconfig.json C:\Users\MyUser.nuget\packages\dotnet-ef.1.9\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll database update --context MyContext --assembly Project.dll --verbose