refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
|
||||
|
||||
/// <summary>
|
||||
/// Provides environment-aware migration behavior for the ScadaBridge configuration database.
|
||||
/// </summary>
|
||||
public static class MigrationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies pending migrations (development mode) or validates schema version (production mode).
|
||||
/// </summary>
|
||||
/// <param name="dbContext">The database context to migrate or validate.</param>
|
||||
/// <param name="isDevelopment">When true, auto-applies migrations. When false, validates schema version matches.</param>
|
||||
/// <param name="logger">Optional logger for readiness-wait diagnostics.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static async Task ApplyOrValidateMigrationsAsync(
|
||||
ScadaBridgeDbContext dbContext,
|
||||
bool isDevelopment,
|
||||
ILogger? logger = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Wait for the target database to accept connections before invoking MigrateAsync.
|
||||
// On a fresh MSSQL container, user databases recover asynchronously after the server
|
||||
// starts accepting connections — DB_ID(@dbName) returns null until recovery completes.
|
||||
// Without this wait, MigrateAsync sees the database as missing and falls through to
|
||||
// CREATE DATABASE, which fails for non-privileged app logins.
|
||||
await WaitForDatabaseReadyAsync(dbContext, logger, cancellationToken);
|
||||
|
||||
if (isDevelopment)
|
||||
{
|
||||
await dbContext.Database.MigrateAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pendingMigrations = await dbContext.Database.GetPendingMigrationsAsync(cancellationToken);
|
||||
var pending = pendingMigrations.ToList();
|
||||
if (pending.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Database schema is out of date. {pending.Count} pending migration(s): {string.Join(", ", pending)}. " +
|
||||
"Apply migrations using 'dotnet ef database update' or the generated SQL scripts before starting in production mode.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitForDatabaseReadyAsync(
|
||||
ScadaBridgeDbContext dbContext,
|
||||
ILogger? logger,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var timeout = TimeSpan.FromSeconds(60);
|
||||
var pollInterval = TimeSpan.FromSeconds(2);
|
||||
var deadline = DateTimeOffset.UtcNow + timeout;
|
||||
var attempt = 0;
|
||||
Exception? lastException = null;
|
||||
|
||||
while (DateTimeOffset.UtcNow < deadline)
|
||||
{
|
||||
attempt++;
|
||||
try
|
||||
{
|
||||
if (await dbContext.Database.CanConnectAsync(cancellationToken))
|
||||
{
|
||||
if (attempt > 1)
|
||||
{
|
||||
logger?.LogInformation(
|
||||
"Configuration database ready after {Attempt} attempt(s).", attempt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
logger?.LogDebug(
|
||||
"Configuration database not yet reachable (attempt {Attempt}).", attempt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
logger?.LogDebug(ex,
|
||||
"Configuration database not yet reachable (attempt {Attempt}).", attempt);
|
||||
}
|
||||
|
||||
await Task.Delay(pollInterval, cancellationToken);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Configuration database not ready after {timeout.TotalSeconds:N0}s ({attempt} attempts). " +
|
||||
"Verify SQL Server is running and the configuration database exists and is attached.",
|
||||
lastException);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user