feeae1371e
- NJ-3: widen per-row catch to Exception (an STJ encode failure can't abort startup); drop dead null-guard already excluded by the SQL filter - NJ-4: capture logger/instanceName in locals for the fire-and-forget normalize continuation (match the sibling pattern in this actor) - NJ-5: emit a warn-log when a malformed List value is imported verbatim; thread an optional ILogger<BundleImporter> to the sync re-import site
119 lines
4.6 KiB
C#
119 lines
4.6 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
|
|
|
|
/// <summary>
|
|
/// Idempotent central startup normalizer that rewrites already-persisted List attribute
|
|
/// values from the old array-of-strings JSON form (<c>["10","20"]</c>) to the new
|
|
/// native-typed form (<c>[10,20]</c>).
|
|
///
|
|
/// <para>
|
|
/// This is a safety net: at the time of writing there is no deployed List attribute data,
|
|
/// so in practice it finds nothing to rewrite. It runs on every central startup after
|
|
/// migrations apply and MUST never abort startup for data reasons — every row's work is
|
|
/// wrapped in a per-row try/catch that logs and skips on malformed data. A second run
|
|
/// finds nothing to change (native → native re-encode is byte-identical).
|
|
/// </para>
|
|
/// </summary>
|
|
public static class ListValueNormalizer
|
|
{
|
|
/// <summary>
|
|
/// Rewrites old-form List attribute values to the native-typed JSON form across
|
|
/// <see cref="ScadaBridgeDbContext.TemplateAttributes"/> and
|
|
/// <see cref="ScadaBridgeDbContext.InstanceAttributeOverrides"/>. Idempotent and
|
|
/// best-effort: malformed rows are logged and skipped, never rethrown.
|
|
/// </summary>
|
|
/// <param name="db">The configuration database context.</param>
|
|
/// <param name="logger">Optional logger for diagnostics.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
public static async Task NormalizeAsync(
|
|
ScadaBridgeDbContext db,
|
|
ILogger? logger = null,
|
|
CancellationToken ct = default)
|
|
{
|
|
var rewritten = 0;
|
|
|
|
// TemplateAttributes: List rows carry the element type on the row itself.
|
|
var templateRows = await db.TemplateAttributes
|
|
.Where(a => a.DataType == DataType.List)
|
|
.ToListAsync(ct);
|
|
|
|
foreach (var a in templateRows)
|
|
{
|
|
try
|
|
{
|
|
var native = AttributeValueCodec.Encode(
|
|
AttributeValueCodec.Decode(a.Value, DataType.List, a.ElementDataType));
|
|
if (native != a.Value)
|
|
{
|
|
a.Value = native;
|
|
rewritten++;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Never abort startup for a single bad row. FormatException from Decode is the
|
|
// expected case; the broad catch also covers an unexpected serialize failure
|
|
// (e.g. a JsonException on a non-finite value) so one poison row can't crash boot.
|
|
logger?.LogWarning(ex,
|
|
"List value normalizer: skipping unprocessable list value for TemplateAttribute {Id}.",
|
|
a.Id);
|
|
}
|
|
}
|
|
|
|
// InstanceAttributeOverrides: only rows that carry an element type are List rows.
|
|
// Rows with a null ElementDataType are scalar/legacy rows (no deployed List data
|
|
// exists, so none in practice) and are skipped.
|
|
var overrideRows = await db.InstanceAttributeOverrides
|
|
.Where(o => o.ElementDataType != null)
|
|
.ToListAsync(ct);
|
|
|
|
foreach (var o in overrideRows)
|
|
{
|
|
try
|
|
{
|
|
var native = AttributeValueCodec.Encode(
|
|
AttributeValueCodec.Decode(o.OverrideValue, DataType.List, o.ElementDataType));
|
|
if (native != o.OverrideValue)
|
|
{
|
|
o.OverrideValue = native;
|
|
rewritten++;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger?.LogWarning(ex,
|
|
"List value normalizer: skipping unprocessable list value for InstanceAttributeOverride {Id}.",
|
|
o.Id);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
await db.SaveChangesAsync(ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// A catastrophic DB failure on SaveChanges may propagate, but log it first so
|
|
// startup diagnostics are not silent. Per-row data problems are already handled
|
|
// above and never reach here.
|
|
logger?.LogError(ex, "List value normalizer: SaveChanges failed.");
|
|
throw;
|
|
}
|
|
|
|
if (rewritten > 0)
|
|
{
|
|
logger?.LogInformation(
|
|
"List value normalizer: rewrote {n} attribute value(s) to native JSON.", rewritten);
|
|
}
|
|
else
|
|
{
|
|
logger?.LogDebug("List value normalizer: no attribute values required rewriting.");
|
|
}
|
|
}
|
|
}
|