Skip to content

Commit 8c9f284

Browse files
authored
Merge pull request #5613 from Particular/john/more_core_things
Enable EF database migrations and advanced persistence configuration
2 parents 6de46c7 + c432d4e commit 8c9f284

7 files changed

Lines changed: 125 additions & 6 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace ServiceControl.Persistence.EFCore.PostgreSql;
2+
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.Extensions.Logging;
5+
using ServiceControl.Persistence.EFCore.Abstractions;
6+
using ServiceControl.Persistence.EFCore.DbContexts;
7+
8+
class PostgreSqlDatabaseMigrator(ServiceControlDbContext dbContext, ILogger<PostgreSqlDatabaseMigrator> logger) : IDatabaseMigrator
9+
{
10+
public async Task ApplyMigrations(CancellationToken cancellationToken = default)
11+
{
12+
logger.LogInformation("Starting PostgreSQL database migration");
13+
14+
var previousTimeout = dbContext.Database.GetCommandTimeout();
15+
dbContext.Database.SetCommandTimeout(EFPersisterSettings.MigrationCommandTimeout);
16+
17+
await dbContext.Database.MigrateAsync(cancellationToken);
18+
19+
dbContext.Database.SetCommandTimeout(previousTimeout);
20+
21+
logger.LogInformation("PostgreSQL database migration completed");
22+
}
23+
}

src/ServiceControl.Persistence.EFCore.PostgreSql/PostgreSqlPersistence.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace ServiceControl.Persistence.EFCore.PostgreSql;
33
using Microsoft.EntityFrameworkCore;
44
using Microsoft.Extensions.DependencyInjection;
55
using ServiceControl.Persistence.EFCore.Abstractions;
6+
using ServiceControl.Persistence.EFCore.DbContexts;
67

78
class PostgreSqlPersistence(PostgreSqlPersisterSettings settings) : BasePersistence, IPersistence
89
{
@@ -17,6 +18,8 @@ public void AddInstaller(IServiceCollection services)
1718
{
1819
RegisterSettings(services);
1920
ConfigureDbContext(services);
21+
22+
services.AddScoped<IDatabaseMigrator, PostgreSqlDatabaseMigrator>();
2023
}
2124

2225
void RegisterSettings(IServiceCollection services)
@@ -26,7 +29,28 @@ void RegisterSettings(IServiceCollection services)
2629
services.AddSingleton(settings);
2730
}
2831

29-
void ConfigureDbContext(IServiceCollection services) =>
30-
services.AddPooledDbContextFactory<PostgreSqlServiceControlDbContext>(options =>
31-
options.UseNpgsql(settings.ConnectionString, npgsql => npgsql.CommandTimeout(settings.CommandTimeout)));
32+
void ConfigureDbContext(IServiceCollection services)
33+
{
34+
services.AddDbContext<PostgreSqlServiceControlDbContext>((serviceProvider, options) =>
35+
{
36+
options.UseNpgsql(settings.ConnectionString, npgsqlOptions =>
37+
{
38+
npgsqlOptions.CommandTimeout(settings.CommandTimeout);
39+
if (settings.EnableRetryOnFailure)
40+
{
41+
npgsqlOptions.EnableRetryOnFailure(
42+
maxRetryCount: settings.MaxRetryCount,
43+
maxRetryDelay: TimeSpan.FromSeconds(settings.MaxRetryDelayInSeconds),
44+
errorCodesToAdd: null);
45+
}
46+
});
47+
48+
if (settings.EnableSensitiveDataLogging)
49+
{
50+
options.EnableSensitiveDataLogging();
51+
}
52+
}, ServiceLifetime.Scoped);
53+
54+
services.AddScoped<ServiceControlDbContext>(provider => provider.GetRequiredService<PostgreSqlServiceControlDbContext>());
55+
}
3256
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace ServiceControl.Persistence.EFCore.SqlServer;
2+
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.Extensions.Logging;
5+
using ServiceControl.Persistence.EFCore.Abstractions;
6+
using ServiceControl.Persistence.EFCore.DbContexts;
7+
8+
class SqlServerDatabaseMigrator(ServiceControlDbContext dbContext, ILogger<SqlServerDatabaseMigrator> logger) : IDatabaseMigrator
9+
{
10+
public async Task ApplyMigrations(CancellationToken cancellationToken = default)
11+
{
12+
logger.LogInformation("Starting SQL Server database migration");
13+
14+
var previousTimeout = dbContext.Database.GetCommandTimeout();
15+
dbContext.Database.SetCommandTimeout(EFPersisterSettings.MigrationCommandTimeout);
16+
17+
await dbContext.Database.MigrateAsync(cancellationToken);
18+
19+
dbContext.Database.SetCommandTimeout(previousTimeout);
20+
21+
logger.LogInformation("SQL Server database migration completed");
22+
}
23+
}

src/ServiceControl.Persistence.EFCore.SqlServer/SqlServerPersistence.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace ServiceControl.Persistence.EFCore.SqlServer;
33
using Microsoft.EntityFrameworkCore;
44
using Microsoft.Extensions.DependencyInjection;
55
using ServiceControl.Persistence.EFCore.Abstractions;
6+
using ServiceControl.Persistence.EFCore.DbContexts;
67

78
class SqlServerPersistence(SqlServerPersisterSettings settings) : BasePersistence, IPersistence
89
{
@@ -17,6 +18,8 @@ public void AddInstaller(IServiceCollection services)
1718
{
1819
RegisterSettings(services);
1920
ConfigureDbContext(services);
21+
22+
services.AddScoped<IDatabaseMigrator, SqlServerDatabaseMigrator>();
2023
}
2124

2225
void RegisterSettings(IServiceCollection services)
@@ -26,7 +29,28 @@ void RegisterSettings(IServiceCollection services)
2629
services.AddSingleton(settings);
2730
}
2831

29-
void ConfigureDbContext(IServiceCollection services) =>
30-
services.AddPooledDbContextFactory<SqlServerServiceControlDbContext>(options =>
31-
options.UseSqlServer(settings.ConnectionString, sqlServer => sqlServer.CommandTimeout(settings.CommandTimeout)));
32+
void ConfigureDbContext(IServiceCollection services)
33+
{
34+
services.AddDbContext<SqlServerServiceControlDbContext>((serviceProvider, options) =>
35+
{
36+
options.UseSqlServer(settings.ConnectionString, sqlOptions =>
37+
{
38+
sqlOptions.CommandTimeout(settings.CommandTimeout);
39+
if (settings.EnableRetryOnFailure)
40+
{
41+
sqlOptions.EnableRetryOnFailure(
42+
maxRetryCount: settings.MaxRetryCount,
43+
maxRetryDelay: TimeSpan.FromSeconds(settings.MaxRetryDelayInSeconds),
44+
errorNumbersToAdd: null);
45+
}
46+
});
47+
48+
if (settings.EnableSensitiveDataLogging)
49+
{
50+
options.EnableSensitiveDataLogging();
51+
}
52+
}, ServiceLifetime.Scoped);
53+
54+
services.AddScoped<ServiceControlDbContext>(provider => provider.GetRequiredService<SqlServerServiceControlDbContext>());
55+
}
3256
}

src/ServiceControl.Persistence.EFCore/Abstractions/EFPersisterSettings.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ namespace ServiceControl.Persistence.EFCore.Abstractions;
22

33
public abstract class EFPersisterSettings : PersistenceSettings
44
{
5+
public static readonly TimeSpan MigrationCommandTimeout = TimeSpan.FromMinutes(40);
6+
57
public required string ConnectionString { get; set; }
68
public int CommandTimeout { get; set; } = 30;
79
public TimeSpan ErrorRetentionPeriod { get; set; }
810
public string? MessageBodyStoragePath { get; set; }
911
public int MinBodySizeForCompression { get; set; } = 4096;
12+
public int MaxRetryCount { get; set; } = 5;
13+
public int MaxRetryDelayInSeconds { get; set; } = 30;
14+
public bool EnableSensitiveDataLogging { get; set; }
15+
public bool EnableRetryOnFailure { get; set; } = true;
1016
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace ServiceControl.Persistence;
2+
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
public interface IDatabaseMigrator
7+
{
8+
Task ApplyMigrations(CancellationToken cancellationToken = default);
9+
}

src/ServiceControl/Hosting/Commands/SetupCommand.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
{
33
using System.Runtime.InteropServices;
44
using System.Threading.Tasks;
5+
using Microsoft.Extensions.DependencyInjection;
56
using Microsoft.Extensions.Hosting;
67
using Microsoft.Extensions.Logging;
78
using Particular.ServiceControl;
89
using Particular.ServiceControl.Hosting;
910
using ServiceBus.Management.Infrastructure.Installers;
1011
using ServiceBus.Management.Infrastructure.Settings;
1112
using ServiceControl.Infrastructure;
13+
using ServiceControl.Persistence;
1214
using Transports;
1315

1416
class SetupCommand : AbstractCommand
@@ -47,6 +49,14 @@ public override async Task Execute(HostArguments args, Settings settings)
4749
await transportCustomization.ProvisionQueues(transportSettings, componentSetupContext.Queues);
4850
}
4951

52+
await using (var scope = host.Services.CreateAsyncScope())
53+
{
54+
if (scope.ServiceProvider.GetService<IDatabaseMigrator>() is { } databaseMigrator)
55+
{
56+
await databaseMigrator.ApplyMigrations();
57+
}
58+
}
59+
5060
await host.StopAsync();
5161
}
5262
}

0 commit comments

Comments
 (0)