{"record":{"id":"ca108cf329721efc","repo":"stride3d/stride","slug":"model-doesn-t-have-expected-parent","errorCode":null,"errorMessage":"Model doesn't have expected parent.","messagePattern":"Model doesn't have expected parent\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Rendering/Rendering/Model.cs","lineNumber":161,"sourceCode":"            result.Skeleton = Skeleton;\n            result.BoundingBox = BoundingBox;\n\n            return result;\n        }\n\n        private void Children_CollectionChanged(object sender, TrackingCollectionChangedEventArgs e)\n        {\n            var child = (Model)e.Item;\n            switch (e.Action)\n            {\n                case NotifyCollectionChangedAction.Add:\n                    if (child.parent != null)\n                        throw new InvalidOperationException(\"Model already have a parent.\");\n                    child.parent = this;\n                    break;\n                case NotifyCollectionChangedAction.Remove:\n                    if (child.parent != this)\n                        throw new InvalidOperationException(\"Model doesn't have expected parent.\");\n                    child.parent = null;\n                    break;\n            }\n        }\n    }\n}\n","sourceCodeStart":143,"sourceCodeEnd":168,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Rendering/Rendering/Model.cs#L143-L168","documentation":"When a Model is removed from a Children collection, Model.Children_CollectionChangeeshanged verifies the child's parent is the collection's owner before clearing it. If child.parent does not match, the internal parent/child bookkeeping is inconsistent and InvalidOperationException is thrown.","triggerScenarios":"Removing a Model from a Children collection when its parent field points elsewhere — e.g. the child was manually re-parented, added to another collection, or its parent was changed without going through Children.","commonSituations":"Mixing manual parent manipulation with Children collection updates; stale removal after the child was moved; deserialization that sets parents directly then mutates collections.","solutions":["Always add/remove children through the parent's Children collection and never set child.parent directly","Ensure the child belongs to this parent before calling Remove (parent.Children.Contains(child))","Re-add the child to the correct parent's collection so the bookkeeping is repaired before removing"],"exampleFix":"// before\nchild.parent = newParent; // breaks invariants\noldParent.Children.Remove(child); // throws\n// after\noldParent.Children.Remove(child); // clears child.parent properly\nnewParent.Children.Add(child);","handlingStrategy":"validation","validationCode":"if (child.parent != this) return; // nothing to remove from this parent's bookkeeping\nparent.Children.Remove(child);","typeGuard":"static bool BelongsTo(Model child, Model owner) => child.parent == owner;","tryCatchPattern":"try { parent.Children.Remove(child); }\ncatch (InvalidOperationException ex) when (ex.Message == \"Model doesn't have expected parent.\") {\n    // bookkeeping corrupted; re-add to correct parent to repair\n    child.parent?.Children.Add(child);\n}","preventionTips":["Mutate the hierarchy only through Children.Add/Remove","Never assign child.parent manually","After deserialization, rebuild parent links via Children rather than direct field writes"],"tags":["rendering","scene-graph","invariant"],"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-23T08:17:48.524Z"}