diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
index 7e8a4b18e1..dd656b8019 100644
--- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
+++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs
@@ -664,6 +664,89 @@ namespace Umbraco.Tests.PublishedContent
AssertDocuments(documents, "N1-fr-FR", "N4-fr-FR", null /*11*/, "N12-fr-FR", null /*5*/, "N6-fr-FR");
}
+ [Test]
+ public void RemoveTest()
+ {
+ Init(GetInvariantKits());
+
+ var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
+ _snapshotAccessor.PublishedSnapshot = snapshot;
+
+ var documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N1", "N2", "N3");
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4", "N5", "N6");
+
+ documents = snapshot.Content.GetById(2).Children().ToArray();
+ AssertDocuments(documents, "N9", "N8", "N7");
+
+ // notify
+ _snapshotService.Notify(new[]
+ {
+ new ContentCacheRefresher.JsonPayload(3, TreeChangeTypes.Remove), // remove last
+ new ContentCacheRefresher.JsonPayload(5, TreeChangeTypes.Remove), // remove middle
+ new ContentCacheRefresher.JsonPayload(9, TreeChangeTypes.Remove), // remove first
+ }, out _, out _);
+
+ documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N1", "N2");
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4", "N6");
+
+ documents = snapshot.Content.GetById(2).Children().ToArray();
+ AssertDocuments(documents, "N8", "N7");
+
+ // notify
+ _snapshotService.Notify(new[]
+ {
+ new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.Remove), // remove first
+ new ContentCacheRefresher.JsonPayload(8, TreeChangeTypes.Remove), // remove
+ new ContentCacheRefresher.JsonPayload(7, TreeChangeTypes.Remove), // remove
+ }, out _, out _);
+
+ documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N2");
+
+ documents = snapshot.Content.GetById(2).Children().ToArray();
+ AssertDocuments(documents);
+ }
+
+ [Test]
+ public void UpdateTest()
+ {
+ Init(GetInvariantKits());
+
+ var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
+ _snapshotAccessor.PublishedSnapshot = snapshot;
+
+ var documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N1", "N2", "N3");
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4", "N5", "N6");
+
+ documents = snapshot.Content.GetById(2).Children().ToArray();
+ AssertDocuments(documents, "N9", "N8", "N7");
+
+ // notify
+ _snapshotService.Notify(new[]
+ {
+ new ContentCacheRefresher.JsonPayload(1, TreeChangeTypes.RefreshBranch),
+ new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.RefreshNode),
+ }, out _, out _);
+
+ documents = snapshot.Content.GetAtRoot().ToArray();
+ AssertDocuments(documents, "N1", "N2", "N3");
+
+ documents = snapshot.Content.GetById(1).Children().ToArray();
+ AssertDocuments(documents, "N4", "N5", "N6");
+
+ documents = snapshot.Content.GetById(2).Children().ToArray();
+ AssertDocuments(documents, "N9", "N8", "N7");
+ }
+
private void AssertDocuments(IPublishedContent[] documents, params string[] names)
{
Assert.AreEqual(names.Length, documents.Length);
diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs
index cfc4fd1106..008bf10504 100644
--- a/src/Umbraco.Web/Models/PublishedContentBase.cs
+++ b/src/Umbraco.Web/Models/PublishedContentBase.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Web.Models
///
/// This base class does which (a) consistently resolves and caches the Url, (b) provides an implementation
/// for this[alias], and (c) provides basic content set management.
- [DebuggerDisplay("Content Id: {Id}}")]
+ [DebuggerDisplay("{Content Id: {Id}}")]
public abstract class PublishedContentBase : IPublishedContent
{
#region ContentType
diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
index f1f8f137fb..e59d332525 100644
--- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
+++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs
@@ -511,6 +511,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
RemoveNodeLocked(existing);
AddNodeLocked(kit.Node);
}
+ else
+ {
+ // replacing existing, handle siblings
+ kit.Node.NextSiblingContentId = existing.NextSiblingContentId;
+ }
_xmap[kit.Node.Uid] = kit.Node.Id;
}
@@ -604,7 +609,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// try to find the content
// if it is not there, nothing to do
- _contentNodes.TryGetValue(id, out LinkedNode link); // else null
+ _contentNodes.TryGetValue(id, out var link); // else null
if (link?.Value == null) return false;
var content = link.Value;
@@ -674,6 +679,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
var parent = parentLink.Value;
+ // must have children
+ if (parent.FirstChildContentId < 0)
+ throw new Exception("panic: no children");
+
+ // if first, clone parent + remove first child
if (parent.FirstChildContentId == content.Id)
{
parent = GenCloneLocked(parentLink);
@@ -681,11 +691,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
else
{
+ // iterate children until the previous child
var link = GetLinkedNode(parent.FirstChildContentId, "first child");
while (link.Value.NextSiblingContentId != content.Id)
- link = GetLinkedNode(parent.NextSiblingContentId, "next child");
+ link = GetLinkedNode(link.Value.NextSiblingContentId, "next child");
+ // clone the previous child and replace next child
var prevChild = GenCloneLocked(link);
prevChild.NextSiblingContentId = content.NextSiblingContentId;
}
@@ -732,6 +744,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
var parent = parentLink.Value;
+ // if parent has no children, clone parent + add as first child
if (parent.FirstChildContentId < 0)
{
parent = GenCloneLocked(parentLink);
@@ -739,39 +752,45 @@ namespace Umbraco.Web.PublishedCache.NuCache
return;
}
- var prevChildLink = GetLinkedNode(parent.FirstChildContentId, "first child");
- var prevChild = prevChildLink.Value;
+ // get parent's first child
+ var childLink = GetLinkedNode(parent.FirstChildContentId, "first child");
+ var child = childLink.Value;
- if (prevChild.SortOrder > content.SortOrder)
+ // if first, clone parent + insert as first child
+ if (child.SortOrder > content.SortOrder)
{
content.NextSiblingContentId = parent.FirstChildContentId;
-
parent = GenCloneLocked(parentLink);
parent.FirstChildContentId = content.Id;
return;
}
- while (prevChild.NextSiblingContentId > 0)
+ // else lookup position
+ while (child.NextSiblingContentId > 0)
{
- var link = GetLinkedNode(prevChild.NextSiblingContentId, "next child");
- var nextChild = link.Value;
+ // get next child
+ var nextChildLink = GetLinkedNode(child.NextSiblingContentId, "next child");
+ var nextChild = nextChildLink.Value;
+ // if here, clone previous + append/insert
if (nextChild.SortOrder > content.SortOrder)
{
content.NextSiblingContentId = nextChild.Id;
- prevChild = GenCloneLocked(prevChildLink);
- prevChild.NextSiblingContentId = content.Id;
+ child = GenCloneLocked(childLink);
+ child.NextSiblingContentId = content.Id;
return;
}
- prevChild = nextChild;
- prevChildLink = link;
+ childLink = nextChildLink;
+ child = nextChild;
}
- prevChild = GenCloneLocked(prevChildLink);
- prevChild.NextSiblingContentId = content.Id;
+ // if last, clone previous + append
+ child = GenCloneLocked(childLink);
+ child.NextSiblingContentId = content.Id;
}
+ // replaces the root node
private void SetRootLocked(ContentNode node)
{
if (_root.Gen != _liveGen)
@@ -784,6 +803,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
+ // set a node (just the node, not the tree)
private void SetValueLocked(ConcurrentDictionary> dict, TKey key, TValue value)
where TValue : class
{