using System; using System.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Polly; namespace Microsoft.AspNetCore.Hosting { public static class IWebHostExtensions { public static bool IsInKubernetes(this IServiceProvider services) { var cfg = services.GetService(); var orchestratorType = cfg.GetValue("OrchestratorType"); return orchestratorType?.ToUpper() == "K8S"; } public static IServiceProvider MigrateDbContext(this IServiceProvider services, Action seeder) where TContext : DbContext { var underK8s = services.IsInKubernetes(); using var scope = services.CreateScope(); var scopeServices = scope.ServiceProvider; var logger = scopeServices.GetRequiredService>(); var context = scopeServices.GetService(); try { logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name); if (underK8s) { InvokeSeeder(seeder, context, scopeServices); } else { var retries = 10; var retry = Policy.Handle() .WaitAndRetry( retryCount: retries, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, timeSpan, retry, ctx) => { logger.LogWarning(exception, "[{prefix}] Error migrating database (attempt {retry} of {retries})", nameof(TContext), retry, retries); }); //if the sql server container is not created on run docker compose this //migration can't fail for network related exception. The retry options for DbContext only //apply to transient exceptions // Note that this is NOT applied when running some orchestrators (let the orchestrator to recreate the failing service) retry.Execute(() => InvokeSeeder(seeder, context, scopeServices)); } logger.LogInformation("Migrated database associated with context {DbContextName}", typeof(TContext).Name); } catch (Exception ex) { logger.LogError(ex, "An error occurred while migrating the database used on context {DbContextName}", typeof(TContext).Name); if (underK8s) { throw; // Rethrow under k8s because we rely on k8s to re-run the pod } } return services; } private static void InvokeSeeder(Action seeder, TContext context, IServiceProvider services) where TContext : DbContext { context.Database.Migrate(); seeder(context, services); } } }