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;
///
/// Idempotent central startup normalizer that rewrites already-persisted List attribute
/// values from the old array-of-strings JSON form (["10","20"]) to the new
/// native-typed form ([10,20]).
///
///
/// 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).
///
///
public static class ListValueNormalizer
{
///
/// Rewrites old-form List attribute values to the native-typed JSON form across
/// and
/// . Idempotent and
/// best-effort: malformed rows are logged and skipped, never rethrown.
///
/// The configuration database context.
/// Optional logger for diagnostics.
/// Cancellation token.
/// A task that represents the asynchronous operation.
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.");
}
}
}