fix(adminui): converter skips name-collisions + disabled relays (review)
This commit is contained in:
@@ -1050,6 +1050,17 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
var scripts = (await db.Scripts.Where(s => scriptIds.Contains(s.ScriptId)).ToListAsync(ct))
|
||||
.ToDictionary(s => s.ScriptId, s => s, StringComparer.Ordinal);
|
||||
|
||||
// Pre-load all existing (EquipmentId, Name) tag pairs so per-candidate name-collision checks
|
||||
// are O(1) HashSet lookups rather than per-row DB queries. The set reflects the state BEFORE
|
||||
// this batch; intra-batch alias-vs-alias collisions per equipment cannot occur because vtag
|
||||
// names are unique within an equipment's VirtualTags table.
|
||||
var existingTagNames = (await db.Tags
|
||||
.Where(t => t.EquipmentId != null)
|
||||
.Select(t => new { t.EquipmentId, t.Name })
|
||||
.ToListAsync(ct))
|
||||
.Select(t => new EquipmentName(t.EquipmentId!, t.Name))
|
||||
.ToHashSet(EquipmentNameComparer.Instance);
|
||||
|
||||
var converted = new List<RelayConversionItem>();
|
||||
var skipped = new List<RelayConversionSkip>();
|
||||
var aliasTagsToAdd = new List<Tag>();
|
||||
@@ -1071,6 +1082,13 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!vtag.Enabled)
|
||||
{
|
||||
skipped.Add(new RelayConversionSkip(vtag.EquipmentId, vtag.Name,
|
||||
"Virtual tag is disabled — convert manually if intended."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vtag.Historize)
|
||||
{
|
||||
skipped.Add(new RelayConversionSkip(vtag.EquipmentId, vtag.Name,
|
||||
@@ -1142,6 +1160,16 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
fullName = rawRef;
|
||||
}
|
||||
|
||||
// Check for a name collision with an already-existing Tag on this equipment. If a Tag with
|
||||
// the same (EquipmentId, Name) exists, converting would violate the unique index and abort
|
||||
// the whole SaveChangesAsync — skip this vtag and report it instead.
|
||||
if (existingTagNames.Contains(new EquipmentName(vtag.EquipmentId, vtag.Name)))
|
||||
{
|
||||
skipped.Add(new RelayConversionSkip(vtag.EquipmentId, vtag.Name,
|
||||
$"A tag named '{vtag.Name}' already exists on this equipment — convert manually if intended."));
|
||||
continue;
|
||||
}
|
||||
|
||||
converted.Add(new RelayConversionItem(vtag.EquipmentId, vtag.Name, fullName, vtag.DataType));
|
||||
|
||||
if (!dryRun)
|
||||
@@ -1186,10 +1214,10 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mints a unique alias <c>TagId</c> following the codebase's only id-minting convention — the
|
||||
/// <c>EQ-</c> equipment-id scheme of <c>"<prefix>-" + Guid.ToString("N")[..12]</c> (decision #125).
|
||||
/// No <c>TAG-</c> minting helper existed before this converter, so a fresh GUID-derived id is used:
|
||||
/// it cannot collide with a removed relay's namespace and needs no read-back uniqueness check.
|
||||
/// Mints a unique alias <c>TagId</c> following the codebase's id-minting convention —
|
||||
/// <c>"<prefix>-" + Guid.ToString("N")[..12]</c> (decision #125). A collision with another
|
||||
/// in-flight id is negligible given the 48-bit GUID prefix; the <c>TagId</c> unique index is the
|
||||
/// backstop.
|
||||
/// </summary>
|
||||
private static string NewTagId() => $"TAG-{Guid.NewGuid().ToString("N")[..12]}";
|
||||
|
||||
@@ -1620,6 +1648,27 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
|
||||
TagConfig = BuildAliasTagConfig(input.FullName),
|
||||
};
|
||||
|
||||
/// <summary>Lightweight key for the pre-loaded (EquipmentId, Name) tag-name collision set.</summary>
|
||||
private readonly record struct EquipmentName(string EquipmentId, string Name);
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparer for <see cref="EquipmentName"/> using ordinal string comparison on both fields,
|
||||
/// matching the database unique index semantics for <c>(EquipmentId, Name)</c>.
|
||||
/// </summary>
|
||||
private sealed class EquipmentNameComparer : IEqualityComparer<EquipmentName>
|
||||
{
|
||||
public static readonly EquipmentNameComparer Instance = new();
|
||||
|
||||
public bool Equals(EquipmentName x, EquipmentName y) =>
|
||||
StringComparer.Ordinal.Equals(x.EquipmentId, y.EquipmentId) &&
|
||||
StringComparer.Ordinal.Equals(x.Name, y.Name);
|
||||
|
||||
public int GetHashCode(EquipmentName obj) =>
|
||||
HashCode.Combine(
|
||||
StringComparer.Ordinal.GetHashCode(obj.EquipmentId),
|
||||
StringComparer.Ordinal.GetHashCode(obj.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decision #122: an equipment may only bind to a driver in the same cluster as its line.
|
||||
/// Policy:
|
||||
|
||||
Reference in New Issue
Block a user