Fix invalid CertificateCheckUrl column errors on existing databases.

Auto-migrate SonicContainer schema on startup, rename legacy typo columns, and use fallback SQL when the column is not yet present.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 13:24:20 +02:00
co-authored by Cursor
parent 76e280d809
commit 0a449727ad
5 changed files with 362 additions and 36 deletions
@@ -0,0 +1,126 @@
using System.Data;
using Microsoft.Data.SqlClient;
namespace ZA.CoreService.ESBCertificateManager.Services;
/// <summary>
/// Stellt sicher, dass optionale Schema-Patches auf bestehenden Datenbanken
/// angewendet wurden (z. B. SonicContainer.CertificateCheckUrl).
/// </summary>
public static class DatabaseSchemaPatcher
{
private static readonly string[] LegacyCertificateCheckUrlColumns =
[
"vertifiaktechecurl",
"VerifyCertificateCheckCurl",
"CertificateCheckCurl",
"ZertifikatCheckCurl"
];
private static bool? _hasCertificateCheckUrlColumn;
public static async Task EnsureCertificateCheckUrlColumnAsync(
string connectionString,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
return;
}
await using SqlConnection connection = new(connectionString);
await connection.OpenAsync(cancellationToken);
if (await ColumnExistsAsync(
connection,
"CertificateCheckUrl",
cancellationToken))
{
_hasCertificateCheckUrlColumn = true;
return;
}
foreach (string legacyColumn in LegacyCertificateCheckUrlColumns)
{
if (!await ColumnExistsAsync(
connection,
legacyColumn,
cancellationToken))
{
continue;
}
await using SqlCommand renameCommand = new(
"""
EXEC sp_rename
N'dbo.SonicContainer.LEGACY_COLUMN',
N'CertificateCheckUrl',
N'COLUMN';
"""
.Replace(
"LEGACY_COLUMN",
legacyColumn,
StringComparison.Ordinal),
connection);
await renameCommand.ExecuteNonQueryAsync(cancellationToken);
_hasCertificateCheckUrlColumn = true;
return;
}
const string addColumnSql = """
ALTER TABLE [dbo].[SonicContainer]
ADD [CertificateCheckUrl] [nvarchar](500) NULL;
""";
await using SqlCommand addCommand = new(addColumnSql, connection);
await addCommand.ExecuteNonQueryAsync(cancellationToken);
_hasCertificateCheckUrlColumn = true;
}
public static async Task<bool> HasCertificateCheckUrlColumnAsync(
string connectionString,
CancellationToken cancellationToken = default)
{
if (_hasCertificateCheckUrlColumn is bool cached)
{
return cached;
}
if (string.IsNullOrWhiteSpace(connectionString))
{
return false;
}
await using SqlConnection connection = new(connectionString);
await connection.OpenAsync(cancellationToken);
_hasCertificateCheckUrlColumn = await ColumnExistsAsync(
connection,
"CertificateCheckUrl",
cancellationToken);
return _hasCertificateCheckUrlColumn.Value;
}
private static async Task<bool> ColumnExistsAsync(
SqlConnection connection,
string columnName,
CancellationToken cancellationToken)
{
await using SqlCommand command = new(
"SELECT COL_LENGTH(N'dbo.SonicContainer', @ColumnName);",
connection);
command.Parameters.Add(
new SqlParameter("@ColumnName", SqlDbType.NVarChar, 128)
{
Value = columnName
});
object? result =
await command.ExecuteScalarAsync(cancellationToken);
return result is not null and not DBNull;
}
}