fix(transport): export instance AreaName — Area-by-name reconciliation was half-shipped (exporter hardcoded null)

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-09 16:09:56 -04:00
parent 721a2e595f
commit f40f52950e
4 changed files with 213 additions and 4 deletions
@@ -1,4 +1,5 @@
using System.Security.Cryptography;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
@@ -72,6 +73,24 @@ public sealed class BundleExporter : IBundleExporter
// folder chain, transitive shared-scripts/external-systems if requested).
var resolved = await _resolver.ResolveAsync(selection, cancellationToken).ConfigureAwait(false);
// 1b. Areas travel by NAME (the importer resolves-or-creates them under the
// target site), so we only need an id→name lookup for the AreaIds the
// exported instances actually reference — a single query over Areas keyed
// to those FKs. Without this the serializer would emit AreaName: null and
// the imported instance would lose its area assignment.
var referencedAreaIds = resolved.Instances
.Select(i => i.AreaId)
.Where(id => id is not null)
.Select(id => id!.Value)
.Distinct()
.ToList();
var areaNameById = referencedAreaIds.Count == 0
? new Dictionary<int, string>()
: await _dbContext.Areas
.Where(a => referencedAreaIds.Contains(a.Id))
.ToDictionaryAsync(a => a.Id, a => a.Name, cancellationToken)
.ConfigureAwait(false);
// 2. Convert to the wire-shaped DTO (strips EF identity, refs-by-name).
// The resolver's site/dataConnection/instance closure is wired
// through via object-initializer — without this the aggregate would
@@ -97,6 +116,9 @@ public sealed class BundleExporter : IBundleExporter
// bundle ships an empty smsConfigs array even when SMS configs were
// selected — mirrors the site/instance fix (review item I3).
SmsConfigurations = resolved.SmsConfigs,
// Area id→name lookup for the exported instances — feeds the serializer's
// AreaName projection so each instance's area travels by name.
AreaNameById = areaNameById,
};
var contentDto = _entitySerializer.ToBundleContent(aggregate);