From eeb62101513f7a351814c45c4bcb0a7b9df3b3bf Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 17 Jun 2026 08:32:03 -0400 Subject: [PATCH] fix(dcl): address MXAccess array attributes with trailing "[]" on write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MXAccess silently no-ops a whole-array write unless the item reference ends in "[]" (e.g. ".MoveInWorkOrderNumbers[]") — the COM Write returns success but the value never commits. Reads work either way, so the bug surfaced only on writes. Mirror the AVEVA MES Camstar API, which registers array tags as ".[]" (scalars have no brackets). WriteAsync now resolves/advises/writes array values against tag + "[]" (scalars unchanged), keeping the original tag for result mapping. Adds IsArrayValue matching the ToMxValue/PadArrayToDeclaredSizeAsync array set. Verified live via mxwrtest against the deployed gateway: bare ref write ok but read-back unchanged; "[]" ref write commits (read-back changes, fresh source timestamp). No RealMxGatewayClient unit harness exists (the gRPC session is concrete) — consistent with how the sibling supervisory/ pad/encode fixes are verified. --- .../Adapters/RealMxGatewayClient.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs index 22b9de16..66f4f1a6 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/Adapters/RealMxGatewayClient.cs @@ -143,7 +143,13 @@ public sealed class RealMxGatewayClient : IMxGatewayClient var orderedTags = new List(writes.Count); foreach (var (tag, value) in writes) { - var handle = await GetOrAddItemHandleAsync(tag, ct).ConfigureAwait(false); + // MXAccess addresses a WHOLE array attribute with a trailing "[]" on the + // item reference. A write to the bare reference is silently dropped — the + // COM Write returns success but the value never commits (reads work + // either way). This mirrors the AVEVA MES Camstar API, which registers + // array tags as ".[]". Scalars keep the bare reference. + var writeRef = IsArrayValue(value) ? tag + "[]" : tag; + var handle = await GetOrAddItemHandleAsync(writeRef, ct).ConfigureAwait(false); // MXAccess requires a supervisory advise on the item before it will // accept a write; without it the worker's synchronous COM Write blocks. // With no write-user context we advise supervisory by default (a @@ -155,13 +161,14 @@ public sealed class RealMxGatewayClient : IMxGatewayClient // rejected and the COM write blocks. Pad list values out to the live // array's slot count before encoding (callers signal the valid count // via a separate scalar, e.g. MoveInNumberWorkOrders). - var toWrite = await PadArrayToDeclaredSizeAsync(tag, value, ct).ConfigureAwait(false); + var toWrite = await PadArrayToDeclaredSizeAsync(writeRef, value, ct).ConfigureAwait(false); entries.Add(new WriteBulkEntry { ItemHandle = handle, Value = ToMxValue(toWrite), UserId = _writeUserId, }); + // Map the per-handle result back to the ORIGINAL caller tag (not writeRef). orderedTags.Add(tag); } @@ -387,6 +394,26 @@ public sealed class RealMxGatewayClient : IMxGatewayClient } } + /// + /// Whether a write value is a multi-element (array) attribute value — i.e. one + /// encodes as an MXAccess array. Such attributes must be + /// addressed with a trailing "[]" on the item reference (see WriteAsync). + /// The set matches the array cases in / + /// ; scalars (string, int, bool, …) are false. + /// + private static bool IsArrayValue(object? value) => value switch + { + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + IReadOnlyList => true, + _ => false, + }; + private async Task> PadAsync(string tag, IReadOnlyList values, T pad, CancellationToken ct) { var size = await GetArraySlotCountAsync(tag, ct).ConfigureAwait(false);