{"record":{"id":"bc63d4349b954e1c","repo":"stride3d/stride","slug":"an-item-has-been-added-to-a-collection-that-does-not-have-a","errorCode":null,"errorMessage":"An item has been added to a collection that does not have a predictable Add. Consider using NonIdentifiableCollectionItemsAttribute on this collection.","messagePattern":"An item has been added to a collection that does not have a predictable Add\\. Consider using NonIdentifiableCollectionItemsAttribute on this collection\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNodeExtended.cs","lineNumber":344,"sourceCode":"                        newValue = setDescriptor.ElementType.Default();\n                    }\n                    if (!itemIds.ContainsKey(newValue))\n                    {\n                        var itemId = restoringId != ItemId.Empty ? restoringId : ItemId.New();\n                        itemIds[newValue] = itemId;\n                    }\n                }\n                break;\n            }\n            case ContentChangeType.CollectionAdd:\n            {\n                // Compute the id we will add for this item\n                var itemId = restoringId != ItemId.Empty ? restoringId : ItemId.New();\n                // Add the id to the proper location (insert or add)\n                if (collectionDescriptor is not null && collectionDescriptor.Category != DescriptorCategory.Set)\n                {\n                    if (e.Index == NodeIndex.Empty)\n                        throw new InvalidOperationException(\"An item has been added to a collection that does not have a predictable Add. Consider using NonIdentifiableCollectionItemsAttribute on this collection.\");\n\n                    itemIds.Insert(e.Index.Int, itemId);\n                }\n                else\n                {\n                    itemIds[e.Index.Value] = itemId;\n                }\n                break;\n            }\n            case ContentChangeType.CollectionRemove:\n            {\n                var itemId = itemIds[e.Index.Value];\n                // update isOverriding, it should be true only if the item being removed exist in the base.\n                isOverriding = isOverriding && (baseNode?.HasId(itemId) ?? false);\n                if (collectionDescriptor is not null && collectionDescriptor.Category != DescriptorCategory.Set)\n                {\n                    removedId = itemIds.DeleteAndShift(e.Index.Int, isOverriding);\n                }","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/assets/Stride.Core.Assets.Quantum/Internal/AssetObjectNodeExtended.cs#L326-L362","documentation":"When an item is added to an identifiable collection, AssetObjectNodeExtended records the new item's id at the position reported by the change event. For ordered lists (non-set categories) an add must carry a concrete NodeIndex (predictable Add/Insert); when the index is Empty the library cannot place the id and throws, suggesting NonIdentifiableCollectionItemsAttribute.","triggerScenarios":"An add notification (OnItemChanged with ItemChangeType.Add) arrives with e.Index == NodeIndex.Empty on a collection whose descriptor Category is not Set — i.e. a list-like collection whose Add does not report where the item lands (e.g. HashSet-like or custom collection without predictable Add).","commonSituations":"Using a collection type whose Add doesn't append at a known index (dict-like or set-like behavior without Set descriptor); a custom collection added to an asset without the NonIdentifiableCollectionItemsAttribute; descriptor category changed between versions.","solutions":["Apply [NonIdentifiableCollectionItemsAttribute] to the offending collection property so ids are tracked by index instead of insertion position.","Replace the collection with a standard List<T> (predictable Add) or a true Set type so the descriptor category matches behavior.","Implement Add on the custom collection so it appends at a deterministic end index and reports NodeIndex accordingly.","If the index can be computed, fix the event source to supply a valid e.Index before raising the change."],"exampleFix":"// before\npublic HashSet<MyItem> Items { get; set; } = new(); // set-like, but descriptor is List category\n// after\n[NonIdentifiableCollectionItems]\npublic HashSet<MyItem> Items { get; set; } = new();","handlingStrategy":"validation","validationCode":"var cat = collectionDescriptor?.Category;\nbool needsIndex = cat != DescriptorCategory.Set;\nbool indexKnown = e.Index != NodeIndex.Empty;\nbool safe = !needsIndex || indexKnown; // otherwise mark collection non-identifiable","typeGuard":"static bool SupportsIdentifiableAdd(ICollectionDescriptor d) =>\n    d == null || d.Category == DescriptorCategory.Set || d is { /* predictable Add */ };","tryCatchPattern":"try { nodeExtended.TrackItemChange(e); }\ncatch (InvalidOperationException) { /* treat collection as non-identifiable */ }","preventionTips":["Use List<T> or Set types for identifiable collections","Add [NonIdentifiableCollectionItems] to set-like collections","Keep descriptor Category consistent with the collection's Add behavior"],"tags":["stride","quantum","assets","collections","events"],"backgroundTag":"unsupported-operation","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"}