+ @* Row actions are disabled while the editor is open so the
+ row under edit (and its siblings) can't be deleted out from
+ under the form, and while a delete is in flight (_busy). *@
+ @onclick="() => BeginEdit(s)" disabled="@(_editing || _busy)">Edit
+ @onclick="() => DeleteSchema(s)" disabled="@(_editing || _busy)">Delete
}
@@ -208,21 +211,34 @@
private async Task DeleteSchema(SharedSchema schema)
{
+ // In-flight guard: an editor-open row action is already disabled in the markup,
+ // but the _busy gate is the authoritative guard against a double-invoked delete
+ // (and mirrors the Save path's guard).
+ if (_busy) return;
+
var confirmed = await Dialog.ConfirmAsync(
"Delete Schema",
$"Delete library schema '{schema.Name}'? References to lib:{schema.Name} will no longer resolve.",
danger: true);
if (!confirmed) return;
- var result = await SchemaLibraryService.DeleteAsync(schema.Id);
- if (result.Success)
+ _busy = true;
+ try
{
- _toast.ShowSuccess($"Schema '{schema.Name}' deleted.");
- await LoadAsync();
+ var result = await SchemaLibraryService.DeleteAsync(schema.Id);
+ if (result.Success)
+ {
+ _toast.ShowSuccess($"Schema '{schema.Name}' deleted.");
+ await LoadAsync();
+ }
+ else
+ {
+ _toast.ShowError(result.Error ?? "Delete failed.");
+ }
}
- else
+ finally
{
- _toast.ShowError(result.Error ?? "Delete failed.");
+ _busy = false;
}
}
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SchemaLibraryQueryService.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SchemaLibraryQueryService.cs
index 66bb1345..2f3d9677 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SchemaLibraryQueryService.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/SchemaLibraryQueryService.cs
@@ -31,8 +31,11 @@ public sealed class SchemaLibraryQueryService : ISchemaLibraryQueryService
var repo = scope.ServiceProvider.GetRequiredService();
var all = await repo.ListAsync(cancellationToken);
- // Ordinal-keyed to match the lib:Name resolver's exact-name lookup. Last-wins on
- // the (DB-unique) name guards against a transient duplicate read.
+ // Ordinal-keyed to match the lib:Name resolver's exact-name lookup. Name is
+ // DB-unique, so a list yields at most one row per name and no real collision
+ // occurs; the indexer assignment is defensive only — should two rows ever share
+ // a name (e.g. a mid-write transient read), the later one in enumeration order
+ // overwrites the earlier rather than throwing.
var map = new Dictionary(StringComparer.Ordinal);
foreach (var schema in all)
{
diff --git a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
index afcc58f0..3366f84b 100644
--- a/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs
@@ -2323,8 +2323,13 @@ public class ManagementActor : ReceiveActor
private static async Task