eabf270d71
Resolve all 622 issues flagged by the enhanced CommentChecker: add missing <returns> tags (incl. the standard phrasing on non-generic Task methods), add missing <summary> tags, and replace misused/redundant <inheritdoc/> on members that override or implement nothing with real documentation. Documentation-only — no behavior change; solution builds clean.
96 lines
4.0 KiB
C#
96 lines
4.0 KiB
C#
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>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
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);
|
|
}
|
|
}
|