Skip to content

Commit b207fa3

Browse files
iOvergaardclaudeAndyButland
authored
Relations: Fix descendants query to exclude parent item (#21162)
* Relations: Fix descendants query to exclude parent item The GetPagedDescendantsInReferences query was using a path LIKE without the comma delimiter, causing it to match both the parent item and its descendants. This resulted in the trash confirmation dialog showing the item being deleted in the "descending items with dependencies" list. Changed the WhereLike call to use ",%" suffix instead of just "%", so the query only matches actual descendants (items whose path starts with the parent's path followed by a comma). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add integration test to verify behaviour. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Andy Butland <abutland73@gmail.com>
1 parent dab88d7 commit b207fa3

File tree

2 files changed

+45
-6
lines changed

2 files changed

+45
-6
lines changed

src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,11 @@ public IEnumerable<RelationItemModel> GetPagedDescendantsInReferences(
282282
.From<NodeDto>()
283283
.Where<NodeDto>(x => x.UniqueId == parentKey);
284284

285-
// Gets the descendants of the parent node
285+
// Gets the descendants of the parent node (using ",%" to exclude the parent itself)
286286
Sql<ISqlContext> subQuery = sqlContext.Sql()
287287
.Select<NodeDto>(x => x.NodeId)
288288
.From<NodeDto>()
289-
.WhereLike<NodeDto>(x => x.Path, subsubQuery);
289+
.WhereLike<NodeDto>(x => x.Path, subsubQuery, ",%");
290290

291291
ISqlSyntaxProvider sx = sqlContext.SqlSyntax;
292292
string[] columns = [

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/TrackedReferencesServiceTests.cs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ internal class TrackedReferencesServiceTests : UmbracoIntegrationTest
2424

2525
private Content Root1 { get; set; }
2626

27+
private Content Child1 { get; set; }
28+
2729
private Content Root2 { get; set; }
2830

2931
private IContentType ContentType { get; set; }
@@ -43,14 +45,22 @@ protected virtual void CreateTestData()
4345
ContentType = new ContentTypeBuilder()
4446
.WithName("Page")
4547
.AddPropertyType()
46-
.WithAlias("ContentPicker")
47-
.WithName("contentPicker")
48+
.WithAlias("contentPicker")
49+
.WithName("ContentPicker")
50+
.WithDataTypeId(1046)
51+
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.ContentPicker)
52+
.Done()
53+
.AddPropertyType()
54+
.WithAlias("contentPicker2")
55+
.WithName("ContentPicker2")
4856
.WithDataTypeId(1046)
4957
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.ContentPicker)
50-
.Done()
58+
.Done()
5159
.Build();
5260

5361
ContentTypeService.Save(ContentType);
62+
ContentType.AllowedContentTypes = [new ContentTypeSort(ContentType.Key, 0, ContentType.Alias)];
63+
ContentTypeService.Save(ContentType);
5464

5565
Root1 = new ContentBuilder()
5666
.WithContentType(ContentType)
@@ -60,12 +70,22 @@ protected virtual void CreateTestData()
6070
ContentService.Save(Root1);
6171
ContentService.Publish(Root1, ["*"]);
6272

73+
Child1 = new ContentBuilder()
74+
.WithContentType(ContentType)
75+
.WithName("Child 1")
76+
.WithParentId(Root1.Id)
77+
.Build();
78+
79+
ContentService.Save(Child1);
80+
ContentService.Publish(Child1, ["*"]);
81+
6382
Root2 = new ContentBuilder()
6483
.WithContentType(ContentType)
6584
.WithName("Root 2")
6685
.WithPropertyValues(new
6786
{
68-
contentPicker = Udi.Create(Constants.UdiEntityType.Document, Root1.Key) // contentPicker is the alias of the property type
87+
contentPicker = Udi.Create(Constants.UdiEntityType.Document, Root1.Key), // contentPicker is the alias of the property type
88+
contentPicker2 = Udi.Create(Constants.UdiEntityType.Document, Child1.Key),
6989
})
7090
.Build();
7191

@@ -104,6 +124,25 @@ public async Task Get_Relations_For_Non_Existing_Page_Returns_Not_Found()
104124
});
105125
}
106126

127+
[Test]
128+
public async Task Get_Descendants_In_References_For_Existing_Page_Returns_Expected_Results()
129+
{
130+
var sut = GetRequiredService<ITrackedReferencesService>();
131+
132+
var actual = await sut.GetPagedDescendantsInReferencesAsync(Root1.Key, UmbracoObjectTypes.Document, 0, 10, true);
133+
134+
Assert.Multiple(() =>
135+
{
136+
Assert.IsTrue(actual.Success);
137+
Assert.AreEqual(GetReferencesOperationStatus.Success, actual.Status);
138+
139+
var itemKeys = actual.Result.Items.Select(x => x.NodeKey).ToList();
140+
Assert.IsFalse(itemKeys.Contains(Root1.Key)); // Should not return the parent itself (see: https://github.com/umbraco/Umbraco-CMS/pull/21162)
141+
Assert.AreEqual(1, itemKeys.Count);
142+
Assert.IsTrue(itemKeys.Contains(Child1.Key));
143+
});
144+
}
145+
107146
[Test]
108147
public async Task Get_Descendants_In_References_For_Non_Existing_Page_Returns_Not_Found()
109148
{

0 commit comments

Comments
 (0)