2017-09-13 14:53:06 +02:00
using Microsoft.EntityFrameworkCore ;
2019-01-29 11:43:15 +01:00
using Microsoft.Extensions.Configuration ;
2017-09-13 14:53:06 +02:00
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Logging ;
2017-11-02 15:03:32 +01:00
using Polly ;
2017-09-13 14:53:06 +02:00
using System ;
2017-11-02 15:03:32 +01:00
using System.Data.SqlClient ;
2017-09-13 14:53:06 +02:00
namespace Microsoft.AspNetCore.Hosting
{
public static class IWebHostExtensions
{
2019-01-29 11:43:15 +01:00
public static bool IsInKubernetes ( this IWebHost webHost )
{
var cfg = webHost . Services . GetService < IConfiguration > ( ) ;
var orchestratorType = cfg . GetValue < string > ( "OrchestratorType" ) ;
return orchestratorType ? . ToUpper ( ) = = "K8S" ;
}
2019-07-22 16:16:57 +02:00
public static IWebHost MigrateDbContext < TContext > ( this IWebHost webHost , Action < TContext , IServiceProvider > seeder ) where TContext : DbContext
2017-09-13 14:53:06 +02:00
{
2019-01-29 11:43:15 +01:00
var underK8s = webHost . IsInKubernetes ( ) ;
2017-09-13 14:53:06 +02:00
using ( var scope = webHost . Services . CreateScope ( ) )
{
var services = scope . ServiceProvider ;
var logger = services . GetRequiredService < ILogger < TContext > > ( ) ;
var context = services . GetService < TContext > ( ) ;
try
{
2019-02-22 15:05:28 +00:00
logger . LogInformation ( "Migrating database associated with context {DbContextName}" , typeof ( TContext ) . Name ) ;
2017-09-13 14:53:06 +02:00
2019-01-29 11:43:15 +01:00
if ( underK8s )
{
InvokeSeeder ( seeder , context , services ) ;
}
else
{
2019-08-05 10:20:20 +02:00
var retries = 10 ;
2019-01-29 11:43:15 +01:00
var retry = Policy . Handle < SqlException > ( )
2019-08-05 10:20:20 +02:00
. WaitAndRetry (
retryCount : retries ,
sleepDurationProvider : retryAttempt = > TimeSpan . FromSeconds ( Math . Pow ( 2 , retryAttempt ) ) ,
onRetry : ( exception , timeSpan , retry , ctx ) = >
{
logger . LogWarning ( exception , "[{prefix}] Exception {ExceptionType} with message {Message} detected on attempt {retry} of {retries}" , nameof ( TContext ) , exception . GetType ( ) . Name , exception . Message , retry , retries ) ;
} ) ;
2017-11-02 15:03:32 +01:00
//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
2019-01-29 11:43:15 +01:00
//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 , services ) ) ;
}
2017-09-13 14:53:06 +02:00
2019-02-22 15:05:28 +00:00
logger . LogInformation ( "Migrated database associated with context {DbContextName}" , typeof ( TContext ) . Name ) ;
2017-09-13 14:53:06 +02:00
}
catch ( Exception ex )
{
2019-02-22 15:05:28 +00:00
logger . LogError ( ex , "An error occurred while migrating the database used on context {DbContextName}" , typeof ( TContext ) . Name ) ;
2019-01-29 11:43:15 +01:00
if ( underK8s )
{
throw ; // Rethrow under k8s because we rely on k8s to re-run the pod
}
2017-09-13 14:53:06 +02:00
}
}
return webHost ;
}
2019-01-29 11:43:15 +01:00
private static void InvokeSeeder < TContext > ( Action < TContext , IServiceProvider > seeder , TContext context , IServiceProvider services )
2019-07-22 16:16:57 +02:00
where TContext : DbContext
2019-01-29 11:43:15 +01:00
{
context . Database . Migrate ( ) ;
seeder ( context , services ) ;
}
2017-09-13 14:53:06 +02:00
}
}