diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs index f0fa6cdf17..910717304c 100644 --- a/src/Umbraco.Core/ContentExtensions.cs +++ b/src/Umbraco.Core/ContentExtensions.cs @@ -37,79 +37,6 @@ namespace Umbraco.Core return dirty.WasPropertyDirty("Published") && entity.Published; } - /// - /// Returns a list of the current contents ancestors, not including the content itself. - /// - /// Current content - /// - /// An enumerable list of objects - public static IEnumerable Ancestors(this IContent content, IContentService contentService) - { - return contentService.GetAncestors(content); - } - - /// - /// Returns the parent of the current content. - /// - /// Current content - /// - /// An object - public static IContent Parent(this IContent content, IContentService contentService) - { - return contentService.GetById(content.ParentId); - } - - #endregion - - #region IMedia - - /// - /// Returns a list of the current medias ancestors, not including the media itself. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Ancestors(this IMedia media, IMediaService mediaService) - { - return mediaService.GetAncestors(media); - } - - - /// - /// Returns a list of the current medias children. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Children(this IMedia media, IMediaService mediaService) - { - return mediaService.GetChildren(media.Id); - } - - - /// - /// Returns a list of the current medias descendants, not including the media itself. - /// - /// Current media - /// - /// An enumerable list of objects - public static IEnumerable Descendants(this IMedia media, IMediaService mediaService) - { - return mediaService.GetDescendants(media); - } - - - /// - /// Returns the parent of the current media. - /// - /// Current media - /// - /// An object - public static IMedia Parent(this IMedia media, IMediaService mediaService) - { - return mediaService.GetById(media.ParentId); - } - #endregion /// diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs index ebb35a43ed..5b64584dc6 100644 --- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs @@ -109,9 +109,14 @@ namespace Umbraco.Core.Services if (withDescendants) { - var descendants = mediaService.GetDescendants(media).ToArray(); - var currentChildren = descendants.Where(x => x.ParentId == media.Id); - SerializeDescendants(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, descendants, currentChildren, xml); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var children = mediaService.GetPagedChildren(media.Id, page++, pageSize, out total); + SerializeChildren(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, children, xml); + } } return xml; @@ -478,7 +483,7 @@ namespace Umbraco.Core.Services } // exports an IMedia item descendants. - private static void SerializeDescendants(IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IMedia[] originalDescendants, IEnumerable children, XElement xml) + private static void SerializeChildren(IMediaService mediaService, IDataTypeService dataTypeService, IUserService userService, ILocalizationService localizationService, IEnumerable urlSegmentProviders, IEnumerable children, XElement xml) { foreach (var child in children) { @@ -486,12 +491,15 @@ namespace Umbraco.Core.Services var childXml = Serialize(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, child); xml.Add(childXml); - // capture id (out of closure) and get the grandChildren (children of the child) - var parentId = child.Id; - var grandChildren = originalDescendants.Where(x => x.ParentId == parentId); - - // recurse - SerializeDescendants(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, originalDescendants, grandChildren, childXml); + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var grandChildren = mediaService.GetPagedChildren(child.Id, page++, pageSize, out total); + // recurse + SerializeChildren(mediaService, dataTypeService, userService, localizationService, urlSegmentProviders, grandChildren, childXml); + } } } } diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 31c2e74fd4..6976a09b76 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -78,13 +78,6 @@ namespace Umbraco.Core.Services /// IMedia GetById(int id); - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - IEnumerable GetChildren(int id); - /// /// Gets a collection of objects by Parent Id /// @@ -158,14 +151,7 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter); - - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(int id); - + /// /// Gets a collection of objects by the Id of the /// @@ -321,13 +307,6 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetAncestors(IMedia media); - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - IEnumerable GetDescendants(IMedia media); - /// /// Gets the parent of the current media as an item. /// diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index d2a74ba37e..2c8e38777b 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -586,9 +586,6 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.ContentTree); @@ -602,27 +599,22 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - return GetPagedDescendants(contentPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } - return GetPagedDescendants(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); + return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } } - private IEnumerable GetPagedDescendants(string contentPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) + private IEnumerable GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.ContentTree); + var query = Query(); + if (!contentPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); - var query = Query(); - if (!contentPath.IsNullOrWhiteSpace()) - query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar)); - - return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); - } + return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } /// @@ -1403,50 +1395,8 @@ namespace Umbraco.Core.Services.Implement private void DeleteLocked(IScope scope, IContent content) { - //TODO: Test this - - // then recursively delete descendants, bottom-up - // just repository.Delete + an event - var stack = new Stack(); - stack.Push(content); - var level = 1; - while (stack.Count > 0) + void DoDelete(IContent c) { - var c = stack.Peek(); - if (c.Level == level) - { - var hasChildren = true; - while (hasChildren) - { - IContent last = null; - const int pageSize = 500; - var page = 0; - var total = long.MaxValue; - var countChildren = 0; - - //get all children of c in pages - while (page * pageSize < total) - { - var children = GetPagedChildren(c.Id, page++, pageSize, out total); - - foreach (var ci in children) - { - countChildren++; - stack.Push(ci); - last = ci; - } - - if (countChildren == 0) - hasChildren = false; //exit, there are no children left to load - else - c = last; //recurse with the last child - } - } - } - - c = stack.Pop(); - level = c.Level; - _documentRepository.Delete(c); var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args, nameof(Deleted)); @@ -1455,6 +1405,18 @@ namespace Umbraco.Core.Services.Implement _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); } + + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + //get descendants - ordered from deepest to shallowest + var descendants = GetPagedDescendants(content.Id, page, pageSize, out total, "Path", Direction.Descending); + foreach (var c in descendants) + DoDelete(c); + } + DoDelete(content); } //TODO: @@ -1683,16 +1645,13 @@ namespace Umbraco.Core.Services.Implement var total = long.MaxValue; while(page * pageSize < total) { - var descendants = GetPagedDescendants(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); + var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); foreach (var descendant in descendants) { moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path // update path and level since we do not update parentId - if (paths.ContainsKey(descendant.ParentId) == false) - Console.WriteLine("oops on " + descendant.ParentId + " for " + content.Path + " " + parent?.Path); descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - Console.WriteLine("path " + descendant.Id + " = " + paths[descendant.Id]); descendant.Level += levelDelta; PerformMoveContentLocked(descendant, userId, trash); } diff --git a/src/Umbraco.Core/Services/Implement/MediaService.cs b/src/Umbraco.Core/Services/Implement/MediaService.cs index da04f41e18..25aa02befa 100644 --- a/src/Umbraco.Core/Services/Implement/MediaService.cs +++ b/src/Umbraco.Core/Services/Implement/MediaService.cs @@ -460,21 +460,6 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Gets a collection of objects by Parent Id - /// - /// Id of the Parent to retrieve Children from - /// An Enumerable list of objects - public IEnumerable GetChildren(int id) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - var query = Query().Where(x => x.ParentId == id); - return _mediaRepository.Get(query).OrderBy(x => x.SortOrder); - } - } - /// /// Gets a collection of objects by Parent Id /// @@ -594,15 +579,10 @@ namespace Umbraco.Core.Services.Implement /// An Enumerable list of objects public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); - if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { scope.ReadLock(Constants.Locks.MediaTree); - var query = Query(); - //if the id is System Root, then just get all if (id != Constants.System.Root) { @@ -612,47 +592,22 @@ namespace Umbraco.Core.Services.Implement totalChildren = 0; return Enumerable.Empty(); } - query.Where(x => x.Path.SqlStartsWith(mediaPath[0].Path + ",", TextColumnType.NVarchar)); + return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } - - return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); + return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter); } } - /// - /// Gets descendants of a object by its Id - /// - /// Id of the Parent to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(int id) + private IEnumerable GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren, string orderBy, Direction orderDirection, bool orderBySystemField, IQuery filter) { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - var media = GetById(id); - if (media == null) - return Enumerable.Empty(); + if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex)); + if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - var pathMatch = media.Path + ","; - var query = Query().Where(x => x.Id != media.Id && x.Path.StartsWith(pathMatch)); - return _mediaRepository.Get(query); - } - } + var query = Query(); + if (!mediaPath.IsNullOrWhiteSpace()) + query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar)); - /// - /// Gets descendants of a object by its Id - /// - /// The Parent object to retrieve descendants from - /// An Enumerable flat list of objects - public IEnumerable GetDescendants(IMedia media) - { - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) - { - scope.ReadLock(Constants.Locks.MediaTree); - var pathMatch = media.Path + ","; - var query = Query().Where(x => x.Id != media.Id && x.Path.StartsWith(pathMatch)); - return _mediaRepository.Get(query); - } + return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, Ordering.By(orderBy, orderDirection, isCustomField: !orderBySystemField)); } /// @@ -865,25 +820,8 @@ namespace Umbraco.Core.Services.Implement private void DeleteLocked(IScope scope, IMedia media) { - // then recursively delete descendants, bottom-up - // just repository.Delete + an event - var stack = new Stack(); - stack.Push(media); - var level = 1; - while (stack.Count > 0) + void DoDelete(IMedia c) { - var c = stack.Peek(); - IMedia[] cc; - if (c.Level == level) - while ((cc = c.Children(this).ToArray()).Length > 0) - { - foreach (var ci in cc) - stack.Push(ci); - c = cc[cc.Length - 1]; - } - c = stack.Pop(); - level = c.Level; - _mediaRepository.Delete(c); var args = new DeleteEventArgs(c, false); // raise event & get flagged files scope.Events.Dispatch(Deleted, this, args); @@ -891,6 +829,18 @@ namespace Umbraco.Core.Services.Implement _mediaFileSystem.DeleteFiles(args.MediaFilesToDelete, // remove flagged files (file, e) => Logger.Error(e, "An error occurred while deleting file attached to nodes: {File}", file)); } + + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while(page * pageSize < total) + { + //get descendants - ordered from deepest to shallowest + var descendants = GetPagedDescendants(media.Id, page, pageSize, out total, "Path", Direction.Descending); + foreach (var c in descendants) + DoDelete(c); + } + DoDelete(media); } //TODO: @@ -1100,8 +1050,8 @@ namespace Umbraco.Core.Services.Implement moves.Add(Tuple.Create(media, media.Path)); // capture original path - // get before moving, in case uow is immediate - var descendants = GetDescendants(media); + //need to store the original path to lookup descendants based on it below + var originalPath = media.Path; // these will be updated by the repo because we changed parentId //media.Path = (parent == null ? "-1" : parent.Path) + "," + media.Id; @@ -1114,14 +1064,21 @@ namespace Umbraco.Core.Services.Implement //paths[media.Id] = media.Path; paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : "-1") : parent.Path) + "," + media.Id; - foreach (var descendant in descendants) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) { - moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path + var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, "Path", Direction.Ascending, true, null); + foreach (var descendant in descendants) + { + moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path - // update path and level since we do not update parentId - descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; - descendant.Level += levelDelta; - PerformMoveMediaLocked(descendant, userId, trash); + // update path and level since we do not update parentId + descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id; + descendant.Level += levelDelta; + PerformMoveMediaLocked(descendant, userId, trash); + } } } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index e5d9e8fc3c..ed87f770ca 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1318,7 +1318,7 @@ namespace Umbraco.Web.Editors xnames.Add(xcontent.Name); if (xcontent.ParentId < -1) xnames.Add("Recycle Bin"); - xcontent = xcontent.Parent(Services.ContentService); + xcontent = Services.ContentService.GetParent(xcontent); } xnames.Reverse(); domainModel.Other = "/" + string.Join("/", xnames); diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index dc744ea361..3dfd435d8f 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -271,7 +271,7 @@ namespace Umbraco.Web.Editors // else proceed as usual long totalChildren; - IMedia[] children; + List children; if (pageNumber > 0 && pageSize > 0) { IQuery queryFilter = null; @@ -287,12 +287,13 @@ namespace Umbraco.Web.Editors id, (pageNumber - 1), pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, - queryFilter).ToArray(); + queryFilter).ToList(); } else { - children = Services.MediaService.GetChildren(id).ToArray(); - totalChildren = children.Length; + //better to not use this without paging where possible, currently only the sort dialog does + children = Services.MediaService.GetPagedChildren(id, 0, int.MaxValue, out var total).ToList(); + totalChildren = children.Count; } if (totalChildren == 0) @@ -663,7 +664,8 @@ namespace Umbraco.Web.Editors " returned null"); //look for matching folder - folderMediaItem = mediaRoot.Children(Services.MediaService).FirstOrDefault(x => x.Name == folderName && x.ContentType.Alias == Constants.Conventions.MediaTypes.Folder); + folderMediaItem = FindInChildren(mediaRoot.Id, folderName, Constants.Conventions.MediaTypes.Folder); + if (folderMediaItem == null) { //if null, create a folder @@ -753,6 +755,23 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK, tempFiles); } + private IMedia FindInChildren(int mediaId, string nameToFind, string contentTypeAlias) + { + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) + { + var children = Services.MediaService.GetPagedChildren(mediaId, page, pageSize, out total); + foreach (var c in children) + { + if (c.Name == nameToFind && c.ContentType.Alias == contentTypeAlias) + return c; + } + } + return null; + } + /// /// Given a parent id which could be a GUID, UDI or an INT, this will resolve the INT /// diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 2be0dce096..14b9c0a465 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -129,7 +129,7 @@ namespace Umbraco.Web.Routing var parent = content; do { - parent = parent.ParentId > 0 ? parent.Parent(contentService) : null; + parent = parent.ParentId > 0 ? contentService.GetParent(parent) : null; } while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index b1a302785d..05be969f92 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -304,10 +304,16 @@ namespace Umbraco.Web.Search // branch if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) { - var descendants = mediaService.GetDescendants(media); - foreach (var descendant in descendants) + const int pageSize = 500; + var page = 0; + var total = long.MaxValue; + while (page * pageSize < total) { - ReIndexForMedia(descendant, descendant.Trashed == false); + var descendants = mediaService.GetPagedDescendants(media.Id, page++, pageSize, out total); + foreach (var descendant in descendants) + { + ReIndexForMedia(descendant, descendant.Trashed == false); + } } } }