{"record":{"id":"2c176f1077cf8508","repo":"stride3d/stride","slug":"an-id-is-both-marked-as-deleted-and-associated-to-a-key-of","errorCode":null,"errorMessage":"An id is both marked as deleted and associated to a key of the collection.","messagePattern":"An id is both marked as deleted and associated to a key of the collection\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdentifiers.cs","lineNumber":129,"sourceCode":"        deletedItems.Add(id);\n    }\n\n    public void UnmarkAsDeleted(ItemId id)\n    {\n        deletedItems.Remove(id);\n    }\n\n    public void Validate(bool isList)\n    {\n        var ids = new HashSet<ItemId>(keyToIdMap.Values);\n        if (ids.Count != keyToIdMap.Count)\n            throw new InvalidOperationException(\"Two elements of the collection have the same id\");\n\n        foreach (var deleted in deletedItems)\n            ids.Add(deleted);\n\n        if (ids.Count != keyToIdMap.Count + deletedItems.Count)\n            throw new InvalidOperationException(\"An id is both marked as deleted and associated to a key of the collection.\");\n    }\n\n    public object? GetKey(ItemId itemId)\n    {\n        object? output = null;\n        // TODO: add indexing by guid to avoid O(n)\n        foreach (var kvp in keyToIdMap)\n        {\n            if (kvp.Value == itemId)\n            {\n                if(output != null)\n                    throw new InvalidOperationException(\"Two elements of the collection have the same id\");\n                output = kvp.Key;\n            }\n        }\n        return output;\n    }\n","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/assets/Stride.Core.Assets/Reflection/CollectionItemIdentifiers.cs#L111-L147","documentation":"Thrown by CollectionItemIdentifiers.Validate when an ItemId appears both in deletedItems and as a live key's id in keyToIdMap. An id cannot simultaneously represent a deleted element and an active one; this state would corrupt id-based change tracking.","triggerScenarios":"Calling Validate after marking an id deleted while the same id is still assigned to a key — e.g. deleting an element but not removing its entry, or re-adding an element with a previously deleted id.","commonSituations":"Custom collection mutation code that removes values but reuses the deleted id; undo/redo implementations replaying add/delete in the wrong order; merging changes where a deleted item was re-added with its old id.","solutions":["When re-adding an item whose id is in deletedItems, remove it from deletedItems or assign a brand-new ItemId.","Before Validate, reconcile: for each key id present in deletedItems, either purge the deletion record or regenerate the id.","Rebuild the CollectionItemIdentifiers from the current collection state to clear stale deletion markers.","Fix undo/redo logic so delete-then-re-add sequences correctly restore the id."],"exampleFix":"// before\nidentifiers.MarkDeleted(itemId);\nidentifiers[\"key\"] = itemId; // id is both deleted and live\n// after\nidentifiers.MarkDeleted(itemId);\nidentifiers.RemoveDeleted(itemId); // or assign ItemId.New()\nidentifiers[\"key\"] = itemId;","handlingStrategy":"validation","validationCode":"foreach (var key in identifiers.Keys.ToList())\n{\n    var id = identifiers[key];\n    if (deletedIds.Contains(id))\n        identifiers[key] = ItemId.New(); // revive with a fresh id\n}\nidentifiers.Validate(isList);","typeGuard":null,"tryCatchPattern":"try { identifiers.Validate(isList); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"deleted and associated\"))\n{\n    // rebuild identifiers from live collection or clear stale deletions\n}","preventionTips":["Remove or clear deletion markers when re-adding an item with the same id.","Assign a new ItemId when resurrecting previously deleted items.","Fix undo/redo ordering so deletes and re-adds are replayed consistently.","Call Validate after batch mutations to catch inconsistencies early."],"tags":["invariant","collections","item-ids"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}