From 05c85bbce56292d42f94919870a8ffe7a32ef2f6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 22 Apr 2019 17:51:07 +0200 Subject: [PATCH] NuCache: children as linked lists --- .../PublishedCache/NuCache/ContentNode.cs | 80 ++---- .../PublishedCache/NuCache/ContentStore.cs | 237 +++++++++++++----- .../NuCache/PublishedContent.cs | 89 ++----- .../PublishedCache/NuCache/Snap/LinkedNode.cs | 2 +- src/Umbraco.Web/PublishedContentExtensions.cs | 6 +- 5 files changed, 227 insertions(+), 187 deletions(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index f7eb2ca19e..58e60cd8ad 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Umbraco.Core.Models.PublishedContent; using Umbraco.Web.PublishedCache.NuCache.DataSource; @@ -9,6 +8,10 @@ namespace Umbraco.Web.PublishedCache.NuCache // internal, never exposed, to be accessed from ContentStore (only!) internal class ContentNode { + // special ctor for root pseudo node + public ContentNode() + { } + // special ctor with no content data - for members public ContentNode(int id, Guid uid, IPublishedContentType contentType, int level, string path, int sortOrder, @@ -22,10 +25,10 @@ namespace Umbraco.Web.PublishedCache.NuCache Path = path; SortOrder = sortOrder; ParentContentId = parentContentId; + FirstChildContentId = -1; + NextSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; - - ChildContentIds = new List(); } public ContentNode(int id, Guid uid, IPublishedContentType contentType, @@ -53,10 +56,10 @@ namespace Umbraco.Web.PublishedCache.NuCache Path = path; SortOrder = sortOrder; ParentContentId = parentContentId; + FirstChildContentId = -1; + NextSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; - - ChildContentIds = new List(); } // two-phase ctor, phase 2 @@ -80,56 +83,29 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - // clone parent - private ContentNode(ContentNode origin, IUmbracoContextAccessor umbracoContextAccessor) + // clone + public ContentNode(ContentNode origin, IPublishedContentType contentType = null) { - // everything is the same, except for the child items - // list which is a clone of the original list - Id = origin.Id; Uid = origin.Uid; - ContentType = origin.ContentType; + ContentType = contentType ?? origin.ContentType; Level = origin.Level; Path = origin.Path; SortOrder = origin.SortOrder; ParentContentId = origin.ParentContentId; + FirstChildContentId = origin.FirstChildContentId; + NextSiblingContentId = origin.NextSiblingContentId; CreateDate = origin.CreateDate; CreatorId = origin.CreatorId; var originDraft = origin.DraftContent; var originPublished = origin.PublishedContent; + DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft); + PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished); - DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft, umbracoContextAccessor); - PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished, umbracoContextAccessor); DraftModel = DraftContent?.CreateModel(); PublishedModel = PublishedContent?.CreateModel(); - - ChildContentIds = new List(origin.ChildContentIds); // needs to be *another* list - } - - // clone with new content type - public ContentNode(ContentNode origin, IPublishedContentType contentType, IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, IUmbracoContextAccessor umbracoContextAccessor) - { - Id = origin.Id; - Uid = origin.Uid; - ContentType = contentType; // change! - Level = origin.Level; - Path = origin.Path; - SortOrder = origin.SortOrder; - ParentContentId = origin.ParentContentId; - CreateDate = origin.CreateDate; - CreatorId = origin.CreatorId; - - var originDraft = origin.DraftContent; - var originPublished = origin.PublishedContent; - - DraftContent = originDraft == null ? null : new PublishedContent(this, originDraft.ContentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); - DraftModel = DraftContent?.CreateModel(); - PublishedContent = originPublished == null ? null : new PublishedContent(this, originPublished.ContentData, publishedSnapshotAccessor, variationContextAccessor, umbracoContextAccessor); - PublishedModel = PublishedContent?.CreateModel(); - - ChildContentIds = origin.ChildContentIds; // can be the *same* list } // everything that is common to both draft and published versions @@ -141,7 +117,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public readonly string Path; public readonly int SortOrder; public readonly int ParentContentId; - public List ChildContentIds; + public int FirstChildContentId; + public int NextSiblingContentId; public readonly DateTime CreateDate; public readonly int CreatorId; @@ -155,23 +132,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public IPublishedContent DraftModel; public IPublishedContent PublishedModel; - public ContentNode CloneParent( - IPublishedSnapshotAccessor publishedSnapshotAccessor, - IUmbracoContextAccessor umbracoContextAccessor) - { - return new ContentNode(this, umbracoContextAccessor); - } - public ContentNodeKit ToKit() - { - return new ContentNodeKit - { - Node = this, - ContentTypeId = ContentType.Id, + => new ContentNodeKit + { + Node = this, + ContentTypeId = ContentType.Id, - DraftData = DraftContent?.ContentData, - PublishedData = PublishedContent?.ContentData - }; - } + DraftData = DraftContent?.ContentData, + PublishedData = PublishedContent?.ContentData + }; } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index 5693bd3204..5b54bb7345 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IVariationContextAccessor _variationContextAccessor; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ConcurrentDictionary> _contentNodes; - private readonly ConcurrentDictionary> _contentRootNodes; + private LinkedNode _root; private readonly ConcurrentDictionary> _contentTypesById; private readonly ConcurrentDictionary> _contentTypesByAlias; private readonly ConcurrentDictionary _xmap; @@ -60,7 +60,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _localDb = localDb; _contentNodes = new ConcurrentDictionary>(); - _contentRootNodes = new ConcurrentDictionary>(); + _root = new LinkedNode(new ContentNode(), 0); _contentTypesById = new ConcurrentDictionary>(); _contentTypesByAlias = new ConcurrentDictionary>(StringComparer.InvariantCultureIgnoreCase); _xmap = new ConcurrentDictionary(); @@ -176,7 +176,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } Rollback(_contentNodes); - Rollback(_contentRootNodes); + RollbackRoot(); Rollback(_contentTypesById); Rollback(_contentTypesByAlias); } @@ -202,6 +202,14 @@ namespace Umbraco.Web.PublishedCache.NuCache if (lockInfo.Taken) Monitor.Exit(_rlocko); } + private void RollbackRoot() + { + if (_root.Gen <= _liveGen) return; + + if (_root.Next != null) + _root = _root.Next; + } + private void Rollback(ConcurrentDictionary> dictionary) where TValue : class { @@ -289,7 +297,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (node == null) continue; var contentTypeId = node.ContentType.Id; if (index.TryGetValue(contentTypeId, out var contentType) == false) continue; - SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor)); + SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType)); } } finally @@ -347,7 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } // perform update of content with refreshed content type - from the kits - // skip missing type, skip missing parents & unbuildable kits - what else could we do? + // skip missing type, skip missing parents & un-buildable kits - what else could we do? // kits are ordered by level, so ParentExits is ok here var visited = new List(); foreach (var kit in kits.Where(x => @@ -358,7 +366,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // replacing the node: must preserve the parents var node = GetHead(_contentNodes, kit.Node.Id)?.Value; if (node != null) - kit.Node.ChildContentIds = node.ChildContentIds; + kit.Node.FirstChildContentId = node.FirstChildContentId; SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); @@ -412,10 +420,10 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var id in contentTypeNodes[contentType.Id]) { - _contentNodes.TryGetValue(id, out LinkedNode link); + _contentNodes.TryGetValue(id, out var link); if (link?.Value == null) continue; - var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor); + var node = new ContentNode(link.Value, contentType); SetValueLocked(_contentNodes, id, node); if (_localDb != null) RegisterChange(id, node.ToKit()); } @@ -455,7 +463,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static LinkedNode GetHead(ConcurrentDictionary> dict, TKey key) where TValue : class { - dict.TryGetValue(key, out LinkedNode link); // else null + dict.TryGetValue(key, out var link); // else null return link; } @@ -464,7 +472,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // ReSharper disable LocalizableElement if (kit.IsEmpty) throw new ArgumentException("Kit is empty.", nameof(kit)); - if (kit.Node.ChildContentIds.Count > 0) + if (kit.Node.FirstChildContentId > 0) throw new ArgumentException("Kit content cannot have children.", nameof(kit)); // ReSharper restore LocalizableElement @@ -476,7 +484,7 @@ namespace Umbraco.Web.PublishedCache.NuCache Lock(lockInfo); // get existing - _contentNodes.TryGetValue(kit.Node.Id, out LinkedNode link); + _contentNodes.TryGetValue(kit.Node.Id, out var link); var existing = link?.Value; // else ignore, what else could we do? @@ -488,7 +496,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // manage children if (existing != null) - kit.Node.ChildContentIds = existing.ChildContentIds; + kit.Node.FirstChildContentId = existing.FirstChildContentId; // set SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); @@ -498,13 +506,13 @@ namespace Umbraco.Web.PublishedCache.NuCache if (existing == null) { // new, add to parent - AddToParentLocked(kit.Node); + AddNodeLocked(kit.Node); } else if (moving) { // moved, remove existing from its parent, add content to its parent - RemoveFromParentLocked(existing); - AddToParentLocked(kit.Node); + RemoveNodeLocked(existing); + AddNodeLocked(kit.Node); } _xmap[kit.Node.Uid] = kit.Node.Id; @@ -515,6 +523,14 @@ namespace Umbraco.Web.PublishedCache.NuCache } } + private void ClearRootLocked() + { + if (_root.Gen < _liveGen) + _root = new LinkedNode(new ContentNode(), _liveGen, _root); + else + _root.Value.FirstChildContentId = -1; + } + // IMPORTANT kits must be sorted out by LEVEL public void SetAll(IEnumerable kits) { @@ -524,18 +540,18 @@ namespace Umbraco.Web.PublishedCache.NuCache Lock(lockInfo); ClearLocked(_contentNodes); - ClearLocked(_contentRootNodes); + ClearRootLocked(); // do NOT clear types else they are gone! //ClearLocked(_contentTypesById); //ClearLocked(_contentTypesByAlias); - // skip missing parents & unbuildable kits - what else could we do? + // skip missing parents & un-buildable kits - what else could we do? foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) { SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddToParentLocked(kit.Node); + AddNodeLocked(kit.Node); _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -555,23 +571,23 @@ namespace Umbraco.Web.PublishedCache.NuCache Lock(lockInfo); // get existing - _contentNodes.TryGetValue(rootContentId, out LinkedNode link); + _contentNodes.TryGetValue(rootContentId, out var link); var existing = link?.Value; // clear if (existing != null) { ClearBranchLocked(existing); - RemoveFromParentLocked(existing); + RemoveNodeLocked(existing); } // now add them all back - // skip missing parents & unbuildable kits - what else could we do? + // skip missing parents & un-buildable kits - what else could we do? foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x))) { SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddToParentLocked(kit.Node); + AddNodeLocked(kit.Node); _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -601,7 +617,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ClearBranchLocked(content); // manage the tree - RemoveFromParentLocked(content); + RemoveNodeLocked(content); return true; } @@ -626,10 +642,13 @@ namespace Umbraco.Web.PublishedCache.NuCache _xmap.TryRemove(content.Uid, out _); - foreach (var childId in content.ChildContentIds) + var id = content.FirstChildContentId; + while (id > 0) { - if (_contentNodes.TryGetValue(childId, out LinkedNode link) == false || link.Value == null) continue; + if (!_contentNodes.TryGetValue(id, out var link) || link.Value == null) + throw new Exception("panic: failed to get child"); ClearBranchLocked(link.Value); + id = link.Value.NextSiblingContentId; } } @@ -641,24 +660,37 @@ namespace Umbraco.Web.PublishedCache.NuCache return link; } - private void RemoveFromParentLocked(ContentNode content) + private void RemoveNodeLocked(ContentNode content) { - // remove from root content index, - // or parent's children index + LinkedNode parentLink; if (content.ParentContentId < 0) { - SetValueLocked(_contentRootNodes, content.Id, null); + parentLink = _root; } else { - // obviously parent has to exist - var link = GetParentLink(content); - var parent = link.Value; - if (link.Gen < _liveGen) - parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor); - parent.ChildContentIds.Remove(content.Id); - if (link.Gen < _liveGen) - SetValueLocked(_contentNodes, parent.Id, parent); + if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) + throw new Exception("panic: failed to get parent"); + } + + var parent = parentLink.Value; + + if (parent.FirstChildContentId == content.Id) + { + parent = GenCloneLocked(parentLink); + parent.FirstChildContentId = content.NextSiblingContentId; + } + else + { + if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var link) || link.Value == null) + throw new Exception("panic: failed to get first child"); + + while (link.Value.NextSiblingContentId != content.Id) + if (!_contentNodes.TryGetValue(parent.NextSiblingContentId, out link) || link.Value == null) + throw new Exception("panic: failed to get next sibling"); + + var prevChild = GenCloneLocked(link); + prevChild.NextSiblingContentId = content.NextSiblingContentId; } } @@ -679,25 +711,90 @@ namespace Umbraco.Web.PublishedCache.NuCache return node?.PublishedModel != null; } - private void AddToParentLocked(ContentNode content) + private ContentNode GenCloneLocked(LinkedNode link) { - // add to root content index, - // or parent's children index + var node = link.Value; + + if (node != null && link.Gen < _liveGen) + { + node = new ContentNode(link.Value); + if (link == _root) + SetRootLocked(node); + else + SetValueLocked(_contentNodes, node.Id, node); + } + + return node; + } + + private void AddNodeLocked(ContentNode content) + { + LinkedNode parentLink; + if (content.ParentContentId < 0) { - // need an object reference... just use this... - SetValueLocked(_contentRootNodes, content.Id, this); + parentLink = _root; } else { - // assume parent has been validated and exists - var link = GetParentLink(content); - var parent = link.Value; - if (link.Gen < _liveGen) - parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor); - parent.ChildContentIds.Add(content.Id); - if (link.Gen < _liveGen) - SetValueLocked(_contentNodes, parent.Id, parent); + if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null) + throw new Exception("panic: failed to get parent"); + } + + var parent = parentLink.Value; + + if (parent.FirstChildContentId < 0) + { + parent = GenCloneLocked(parentLink); + parent.FirstChildContentId = content.Id; + return; + } + + if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var prevChildLink) || prevChildLink.Value == null) + throw new Exception("panic: failed to get first child"); + + var prevChild = prevChildLink.Value; + + if (prevChild.SortOrder > content.SortOrder) + { + content.NextSiblingContentId = parent.FirstChildContentId; + + parent = GenCloneLocked(parentLink); + parent.FirstChildContentId = content.Id; + return; + } + + while (prevChild.NextSiblingContentId > 0) + { + if (!_contentNodes.TryGetValue(prevChild.NextSiblingContentId, out var link) || link.Value == null) + throw new Exception("panic: failed to get next child"); + var nextChild = link.Value; + + if (nextChild.SortOrder > content.SortOrder) + { + content.NextSiblingContentId = nextChild.Id; + prevChild = GenCloneLocked(prevChildLink); + prevChild.NextSiblingContentId = content.Id; + return; + } + + prevChild = nextChild; + prevChildLink = link; + } + + prevChild = GenCloneLocked(prevChildLink); + prevChild.NextSiblingContentId = content.Id; + } + + private void SetRootLocked(ContentNode node) + { + if (_root.Gen != _liveGen) + { + _root = new LinkedNode(node, _liveGen, _root); + } + else + { + _root.Value = node; } } @@ -764,18 +861,24 @@ namespace Umbraco.Web.PublishedCache.NuCache public IEnumerable GetAtRoot(long gen) { - // look ma, no lock! - foreach (var kvp in _contentRootNodes) + var z = _root; + while (z != null) { - var link = kvp.Value; - while (link != null) - { - if (link.Gen <= gen) - break; - link = link.Next; - } - if (link?.Value != null) - yield return Get(kvp.Key, gen); + if (z.Gen <= gen) + break; + z = z.Next; + } + if (z == null) + yield break; + + var id = z.Value.FirstChildContentId; + + while (id > 0) + { + if (!_contentNodes.TryGetValue(id, out var link) || link == null) + throw new Exception("panic: failed to get sibling"); + yield return link.Value; + id = link.Value.NextSiblingContentId; } } @@ -928,7 +1031,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return _collectTask; // ReSharper disable InconsistentlySynchronizedField - var task = _collectTask = Task.Run(() => Collect()); + var task = _collectTask = Task.Run(Collect); _collectTask.ContinueWith(_ => { lock (_rlocko) @@ -957,11 +1060,19 @@ namespace Umbraco.Web.PublishedCache.NuCache } Collect(_contentNodes); - Collect(_contentRootNodes); + CollectRoot(); Collect(_contentTypesById); Collect(_contentTypesByAlias); } + private void CollectRoot() + { + var link = _root; + while (link.Next != null && link.Next.Gen > _floorGen) + link = link.Next; + link.Next = null; + } + private void Collect(ConcurrentDictionary> dict) where TValue : class { diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index c1215c881b..3ededddba3 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -67,12 +67,9 @@ namespace Umbraco.Web.PublishedCache.NuCache return user?.Name; } - // (see ContentNode.CloneParent) - public PublishedContent( - ContentNode contentNode, - PublishedContent origin, - IUmbracoContextAccessor umbracoContextAccessor) - : base(umbracoContextAccessor) + // used when cloning in ContentNode + public PublishedContent(ContentNode contentNode, PublishedContent origin) + : base(origin.UmbracoContextAccessor) { _contentNode = contentNode; _publishedSnapshotAccessor = origin._publishedSnapshotAccessor; @@ -124,43 +121,11 @@ namespace Umbraco.Web.PublishedCache.NuCache return GetContentByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); } - private IEnumerable GetContentByIds(bool previewing, IEnumerable ids) - { - var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; - - // beware! the loop below CANNOT be converted to query such as: - //return ids.Select(x => _getContentByIdFunc(publishedSnapshot, previewing, x)).Where(x => x != null); - // because it would capture the published snapshot and cause all sorts of issues - // - // we WANT to get the actual current published snapshot each time we run - - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var id in ids) - { - var content = GetContentByIdFunc(publishedSnapshot, previewing, id); - if (content != null) yield return content; - } - } - private IPublishedContent GetMediaById(bool previewing, int id) { return GetMediaByIdFunc(_publishedSnapshotAccessor.PublishedSnapshot, previewing, id); } - private IEnumerable GetMediaByIds(bool previewing, IEnumerable ids) - { - var publishedShapshot = _publishedSnapshotAccessor.PublishedSnapshot; - - // see note above for content - - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var id in ids) - { - var content = GetMediaByIdFunc(publishedShapshot, previewing, id); - if (content != null) yield return content; - } - } - #endregion #region Content Type @@ -343,43 +308,34 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override IEnumerable Children(string culture = null) { - // FIXME THIS CANNOT WORK - // we cannot cache children this way, they should be a linked list! - throw new NotImplementedException(); + Func getById; - var cache = GetAppropriateCache(); - if (cache == null || PublishedSnapshotService.CachePublishedContentChildren == false) - return GetChildren(); - - // note: ToArray is important here, we want to cache the result, not the function! - return (IEnumerable)cache.Get(ChildrenCacheKey, () => GetChildren().ToArray()); - } - - private string _childrenCacheKey; - - private string ChildrenCacheKey => _childrenCacheKey ?? (_childrenCacheKey = CacheKeys.PublishedContentChildren(Key, IsPreviewing)); - - private IEnumerable GetChildren() - { - IEnumerable c; - switch (_contentNode.ContentType.ItemType) + switch (ContentType.ItemType) { case PublishedItemType.Content: - c = GetContentByIds(IsPreviewing, _contentNode.ChildContentIds); + getById = GetContentByIdFunc; break; case PublishedItemType.Media: - c = GetMediaByIds(IsPreviewing, _contentNode.ChildContentIds); + getById = GetMediaByIdFunc; break; default: - throw new Exception("oops"); + throw new Exception("panic: invalid item type"); } - return c.OrderBy(x => x.SortOrder); + var publishedSnapshot = _publishedSnapshotAccessor.PublishedSnapshot; + var id = _contentNode.FirstChildContentId; - // notes: - // _contentNode.ChildContentIds is an unordered int[] - // needs to fetch & sort - do it only once, lazily, though - // Q: perfs-wise, is it better than having the store managed an ordered list + while (id > 0) + { + var content = (PublishedContent) getById(publishedSnapshot, IsPreviewing, id); + if (content == null) + throw new Exception("panic: failed to get content"); + + if (content.IsInvariantOrHasCulture(culture)) + yield return content; + + id = content._contentNode.NextSiblingContentId; + } } #endregion @@ -439,7 +395,8 @@ namespace Umbraco.Web.PublishedCache.NuCache // used by navigable content // includes all children, published or unpublished // NavigableNavigator takes care of selecting those it wants - internal IList ChildIds => _contentNode.ChildContentIds; + // note: this is not efficient - we do not try to be (would require a double-linked list) + internal IList ChildIds => Children().Select(x => x.Id).ToList(); // used by Property // gets a value indicating whether the content or media exists in diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs index 20d7e7ddcd..78e0ec8d33 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Snap/LinkedNode.cs @@ -17,4 +17,4 @@ public volatile TValue Value; public volatile LinkedNode Next; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 15d4432a90..2fc449731d 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -172,7 +172,11 @@ namespace Umbraco.Web /// /// Culture is case-insensitive. public static bool HasCulture(this IPublishedContent content, string culture) - => content.Cultures.Contains(culture ?? string.Empty); + => content.Cultures.Contains(culture ?? string.Empty); // fixme oops?! + + // fixme + public static bool IsInvariantOrHasCulture(this IPublishedContent content, string culture) + => !content.ContentType.VariesByCulture() || content.Cultures.Contains(culture ?? ""); /// /// Filters a sequence of to return invariant items, and items that are published for the specified culture.