在使用 Entity Framework 进行数据库迁移期间,如何避免在我的 .NET 5 WebApp 中出现 Windows 服务错误 1053?

How can I avoid Windows Service error 1053 in my .NET 5 WebApp during database migration with Entity Framework?

我的 .NET 5 WebApp 作为 Windows 服务运行,并使用 Entity Framework 在启动期间播种和更新(迁移)其数据库。在调用 Host.Run() 之前,我确保我的数据库已更新。这在过去的一年里运行得非常好。现在,我有一个需要几分钟才能完成的大型数据库更新。在此数据库更新期间,Windows 服务将关闭并出现错误 1053,表示超时。我怀疑这是由于运行时未在给定的默认超时期限(看起来大约 30 秒)内到达对 Host.Run() 的调用。问题是我必须在调用 Host.Run() 之前执行这些数据库更新,因为在对数据库进行任何访问之前应该正确更新数据库。

这个问题最简单的解决方法是什么?我可以尝试编写自定义服务生命周期来增加超时。我可以在 Host.Run() 之后移动要执行的数据库更新,并在执行时增加限制访问的额外开销。我还不喜欢这两种解决方案中的任何一种,并寻求更好的选择。也许我的假设也完全错误。下面提供了我的代码。

public class Program
{
    public static async Task Main(string[] args)
    {
        IHost host = CreateHostBuilder(args).Build();
       
        using (IServiceScope scope = host.Services.CreateScope())
        {
            IServiceProvider services = scope.ServiceProvider;
            SeedAndUpdateDb seed = services.GetRequiredService<SeedAndUpdateDb>();
            await seed.InitializeAsync(); //<- This call takes a few minutes to complete
        }

        await host.RunAsync();
    }
    
    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var hostingConfig = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json")
            .Build();
            
        return Host.CreateDefaultBuilder(args)
            .UseWindowsService()
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
                webBuilder.ConfigureKestrel(serverOptions =>
                {
                    serverOptions.Configure(hostingConfig.GetSection("Kestrel"));
                });
                webBuilder.UseKestrel(options =>
                { });
            });
    }
}

底线是这样的:如果您的 运行ning 可执行文件要被识别为启动的 Windows 服务,它必须(作为服务安装并启动后)在服务时startup 告诉 Windows Service Control Manager 它实际上是一个服务并且已经成功启动。

如果它没有及时启动,您会收到可怕的错误消息,指出服务没有及时启动。

您对 .UseWindowsService() 的调用设置了 SCM 的所有管道,但对 SCM 的调用仅在 await host.RunAsync() 中的 .NET Core 主机启动代码中进行。

因此,如果您的可执行文件启动(由于您启动服务)和您的代码调用 await host.RunAsync() 之间的时间过长,您的可执行文件将 运行ning 而您的 托管服务最终会运行,但就Windows而言,您的Windows服务没有启动(及时).

解决方案是使用命令行标志调用迁移,而不是在每次启动时调用。或者在您的托管服务中调用一次迁移,所以当 Windows 服务已经 运行ning.

对于命令行方法:因此在部署之后,您可以在启动 Windows 服务之前 运行 Your-Service.exe --migrate

像这样:

public static async Task Main(string[] args)
{
    IHost host = CreateHostBuilder(args).Build();
   
    if (args.Any(a => a == "--migrate"))
    {
        using (IServiceScope scope = host.Services.CreateScope())
        {
            IServiceProvider services = scope.ServiceProvider;
            SeedAndUpdateDb seed = services.GetRequiredService<SeedAndUpdateDb>();
            await seed.InitializeAsync(); //<- This call takes a few minutes to complete
        }

        Console.WriteLine("Database migrated, you can now start the service");
    }
    else
    {
        await host.RunAsync();
    }
}