Backend Gateway ↔ Child Association
Association authority is server-side. Firmware consumes only the effective current state through onboarding/sync.
Canonical model
GatewayChildAssociation in IOT.App:
Id Guid
ProjectId Guid (real relational FK)
GatewayDeviceSerialId Guid (cross-service reference)
ChildDeviceSerialId Guid (cross-service reference)
BoundAtUtc DateTime
UnboundAtUtc DateTime?
UnbindReason bounded string
ConcurrencyToken rowversion
GatewayDeviceSerialId and ChildDeviceSerialId reference IOT.Center.DeviceSerial.Id. Because service databases are separate, these are not cross-database SQL foreign keys.
Active association
UnboundAtUtc == null
A SQL Server filtered unique index guarantees at most one active Gateway per Child:
builder.HasIndex(x => x.ChildDeviceSerialId)
.IsUnique()
.HasFilter("[UnboundAtUtc] IS NULL");
Routing
Child DeviceSerial
→ active GatewayChildAssociation
→ GatewayDeviceSerialId
→ Physical Gateway DeviceSerial
→ dn/cmd
Never route using the latest historical row.
Delete and reassociation
- unbind closes the row by setting
UnboundAtUtc/reason; it does not delete history - reassociation closes the old row and creates a new one
- deleting a Gateway never deletes Child identity/history
- local Gateway factory reset does not mutate Backend association by itself