When importing or otherwise assigning a Parent to a newly constructed Topic, SetParent() can incorrectly throw ArgumentOutOfRangeException ("A descendant cannot be its own parent") for topics whose Key is a case-insensitive prefix of the target parent's UniqueKey, even when the topic isn't actually an ancestor or descendant of that parent. This happens because a new topic's GetUniqueKey() returns only its own Key until Parent is assigned, so the guard in SetParent() ends up comparing that key against the full parent path using StartsWith(). Any topic keyed R, Ro, Roo, or Root (case-insensitive) anywhere in a site whose root topic is named Root will therefore fail to be created.
Background
The TopicFactory.Create() factory method invokes the Topic constructor, which sets Key before assigning Parent. At the moment Parent is set, SetParent() runs:
if (parent.GetUniqueKey().StartsWith(GetUniqueKey(), StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentOutOfRangeException(nameof(parent), "A descendant cannot be its own parent.");
}
Since the new topic isn't yet assigned to a parent, GetUniqueKey() returns e.g., "RO". The check becomes "Root:Configuration:Metadata:Country:LookupList".StartsWith("RO", OrdinalIgnoreCase), which evaluates true—not because RO is an actual ancestor, but because Root case-insensitively starts with Ro.
Implementation Notes
The guard should only treat parent as a descendant of the current topic when the prefix matches a full path segment boundary, not any substring. The character following the matched prefix must thus be either the end of the string or the : delimiter.
Affected Files
OnTopic/Topic.cs (SetParent())
Proposed Fix
var uniqueKey = GetUniqueKey();
var parentKey = parent.GetUniqueKey();
if (
parentKey.StartsWith(uniqueKey, StringComparison.OrdinalIgnoreCase) &&
parentKey.Length > uniqueKey.Length &&
parentKey[uniqueKey.Length] == ':'
) {
throw new ArgumentOutOfRangeException(nameof(parent), "A descendant cannot be its own parent.");
}
Tasks
When importing or otherwise assigning a
Parentto a newly constructedTopic,SetParent()can incorrectly throwArgumentOutOfRangeException("A descendant cannot be its own parent") for topics whoseKeyis a case-insensitive prefix of the target parent'sUniqueKey, even when the topic isn't actually an ancestor or descendant of that parent. This happens because a new topic'sGetUniqueKey()returns only its ownKeyuntilParentis assigned, so the guard inSetParent()ends up comparing that key against the full parent path usingStartsWith(). Any topic keyedR,Ro,Roo, orRoot(case-insensitive) anywhere in a site whose root topic is namedRootwill therefore fail to be created.Background
The
TopicFactory.Create()factory method invokes theTopicconstructor, which setsKeybefore assigningParent. At the momentParentis set,SetParent()runs:Since the new topic isn't yet assigned to a parent,
GetUniqueKey()returns e.g.,"RO". The check becomes"Root:Configuration:Metadata:Country:LookupList".StartsWith("RO", OrdinalIgnoreCase), which evaluatestrue—not becauseROis an actual ancestor, but becauseRootcase-insensitively starts withRo.Implementation Notes
The guard should only treat
parentas a descendant of the current topic when the prefix matches a full path segment boundary, not any substring. The character following the matched prefix must thus be either the end of the string or the:delimiter.Affected Files
OnTopic/Topic.cs(SetParent())Proposed Fix
Tasks
SetParent()to require a:delimiter (or end of string) after the matched prefixRo(or similar) being created anywhere under a topic namedRoot