fix(transport): skip unbound (empty ConnectionName) bindings on import instead of writing FK=0 (M8 INT review)

This commit is contained in:
Joseph Doherty
2026-06-18 07:56:00 -04:00
parent 4d888c63a3
commit 3782ebdadb
@@ -3237,25 +3237,28 @@ public sealed class BundleImporter : IBundleImporter
} }
foreach (var b in dto.ConnectionBindings) foreach (var b in dto.ConnectionBindings)
{ {
// A binding may carry NO connection name — an unbound attribute, or a
// binding whose source connection was not included in the export. There
// is no meaningful target FK to write (DataConnectionId = 0 would be an
// invalid FK and FK-violate at commit on a relational provider), so skip
// the binding row entirely rather than persist a broken binding.
if (string.IsNullOrEmpty(b.ConnectionName))
{
continue;
}
// Resolve ConnectionName → target DataConnectionId. After the C1 Pass-2 // Resolve ConnectionName → target DataConnectionId. After the C1 Pass-2
// in ApplyDataConnectionsAsync, the map carries an entry for every // in ApplyDataConnectionsAsync, the map carries an entry for every
// referenced connection — carried in the bundle OR auto-matched in the // referenced connection — carried in the bundle OR auto-matched in the
// target. A binding whose connection name STILL doesn't resolve is a // target. A non-empty name that STILL doesn't resolve is a structural
// structural error (it should already have been a preview blocker + // error (it should already have been a preview blocker + a pre-write
// a pre-write validation failure); THROW rather than write // validation failure); THROW rather than write an invalid FK.
// DataConnectionId = 0, which would be an invalid FK on a relational if (!connectionMaps.IdBySourceRef.TryGetValue((sourceSiteIdentifier, b.ConnectionName), out var connId))
// provider and a silently-broken binding on the in-memory one.
// A binding may legitimately carry NO connection name (unbound
// attribute) — only a NON-EMPTY name that fails to resolve is an error.
if (!string.IsNullOrEmpty(b.ConnectionName)
&& !connectionMaps.IdBySourceRef.TryGetValue((sourceSiteIdentifier, b.ConnectionName), out _))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"Instance '{inst.UniqueName}' binding for attribute '{b.AttributeName}' references " $"Instance '{inst.UniqueName}' binding for attribute '{b.AttributeName}' references "
+ $"connection '{sourceSiteIdentifier}/{b.ConnectionName}' which could not be resolved " + $"connection '{sourceSiteIdentifier}/{b.ConnectionName}' which could not be resolved "
+ "to a target connection (present in neither bundle nor target)."); + "to a target connection (present in neither bundle nor target).");
} }
connectionMaps.IdBySourceRef.TryGetValue((sourceSiteIdentifier, b.ConnectionName), out var connId);
inst.ConnectionBindings.Add(new InstanceConnectionBinding(b.AttributeName) inst.ConnectionBindings.Add(new InstanceConnectionBinding(b.AttributeName)
{ {
DataConnectionId = connId, DataConnectionId = connId,