using Microsoft.EntityFrameworkCore; namespace ZB.MOM.WW.OtOpcUa.Configuration.Validation; /// /// Materialises a from the live config DB so /// can run against the current edit state at deploy time. /// /// /// /// This is a whole-DB ("global") snapshot — every cluster's rows in one pass — which is /// what the deploy path needs: the admin-operations actor snapshots and flattens the /// entire config, not one cluster. The validator's rules compare entity-level /// ClusterId fields against each other (e.g. namespace binding), so the snapshot's /// own is not read by any rule and is left empty. /// /// /// is a placeholder (the generation model was /// dropped); no rule reads it. is empty because /// there is no prior-generation table to diff against. /// / are left null so the path-length rule uses its /// conservative upper bound. /// /// public static class DraftSnapshotFactory { /// Builds a from the current config DB rows. /// The config DB context to read from. /// Cancellation token. /// A snapshot populated from the live DB, ready for . public static async Task FromConfigDbAsync(OtOpcUaConfigDbContext db, CancellationToken ct = default) => new DraftSnapshot { // GenerationId=0 and null Enterprise/Site are intentional conservative fallbacks at the // global-factory level: the path-length validator uses its upper bound, and cross-gen // EquipmentUuid checks run in separate validator passes — no rule here reads these fields. GenerationId = 0, // generation model dropped; placeholder (no rule reads it) ClusterId = string.Empty, // global snapshot; rules compare entity ClusterId fields, not this // v3: Namespace entity retired — no namespace list to materialize. The Raw tree // (RawFolders/TagGroups) + UnsTagReferences feed the v3 rules (charset, tagname length, // effective-leaf uniqueness). RawFolders = await db.RawFolders.AsNoTracking().ToListAsync(ct), DriverInstances = await db.DriverInstances.AsNoTracking().ToListAsync(ct), Devices = await db.Devices.AsNoTracking().ToListAsync(ct), TagGroups = await db.TagGroups.AsNoTracking().ToListAsync(ct), UnsAreas = await db.UnsAreas.AsNoTracking().ToListAsync(ct), UnsLines = await db.UnsLines.AsNoTracking().ToListAsync(ct), Equipment = await db.Equipment.AsNoTracking().ToListAsync(ct), Tags = await db.Tags.AsNoTracking().ToListAsync(ct), UnsTagReferences = await db.UnsTagReferences.AsNoTracking().ToListAsync(ct), VirtualTags = await db.VirtualTags.AsNoTracking().ToListAsync(ct), ScriptedAlarms = await db.ScriptedAlarms.AsNoTracking().ToListAsync(ct), Scripts = await db.Scripts.AsNoTracking().ToListAsync(ct), PollGroups = await db.PollGroups.AsNoTracking().ToListAsync(ct), PriorEquipment = [], // intentional: no prior-generation table to diff against at this level ActiveReservations = await db.ExternalIdReservations .AsNoTracking() .Where(r => r.ReleasedAt == null) // active only — matches DraftSnapshot.ActiveReservations semantics .ToListAsync(ct), }; }