From 2a4e73c65021cf9c22e0b68001030d559ac159cd Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 23 Jan 2017 00:40:24 +1100 Subject: [PATCH] Reduced allocations for the Media/Content/Property Factories since we don't need to create new objects every time we want to map values. Changes default sort order for paging from Path to umbracoNode.id since we have no index on Path and it doesn't make a lot of sense. Fixes obsolete warnings for various Sql usages. Reduces the amount of DeepClone calls required when looking up a content type for a content item, even though these are cached they are still deep cloned out of the cache. Fixes the main issue of having nearly 100,000 rows of unsorted property data and then having to query those rows for every document being built, the re-iteration of these rows causes a lot of overhead and is unecessary, instead we ensure the property data set and the document data set is sorted by node id, then use a stored index to continue looking up the property data for the next content item found. --- .../Persistence/Factories/ContentFactory.cs | 17 +- .../Persistence/Factories/MediaFactory.cs | 13 +- .../Persistence/Factories/PropertyFactory.cs | 16 +- .../Repositories/ContentRepository.cs | 52 +++--- .../Repositories/MediaRepository.cs | 60 +++---- .../Repositories/VersionableRepositoryBase.cs | 150 +++++++++++------- src/Umbraco.Core/Services/ContentService.cs | 4 +- src/Umbraco.Core/Services/IContentService.cs | 4 +- src/Umbraco.Core/Services/IMediaService.cs | 4 +- src/Umbraco.Core/Services/MediaService.cs | 4 +- 10 files changed, 199 insertions(+), 125 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs index 5dcec8fed0..88de5a1d6c 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentFactory.cs @@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public IContent BuildEntity(DocumentDto dto) + public static IContent BuildEntity(DocumentDto dto, IContentType contentType) { - var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, _contentType); + var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType); try { content.DisableChangeTracking(); - content.Id = _id; + content.Id = dto.NodeId; content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId; content.Name = dto.Text; content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text; @@ -49,8 +49,8 @@ namespace Umbraco.Core.Persistence.Factories content.Published = dto.Published; content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate; content.UpdateDate = dto.ContentVersionDto.VersionDate; - content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null; - content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null; + content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null; + content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null; content.Version = dto.ContentVersionDto.VersionId; content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished; content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId; @@ -64,6 +64,13 @@ namespace Umbraco.Core.Persistence.Factories { content.EnableChangeTracking(); } + + } + + [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] + public IContent BuildEntity(DocumentDto dto) + { + return BuildEntity(dto, _contentType); } public DocumentDto BuildDto(IContent entity) diff --git a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs index 0fcb654cb7..5729bb125e 100644 --- a/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MediaFactory.cs @@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories #region Implementation of IEntityFactory - public IMedia BuildEntity(ContentVersionDto dto) + public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType) { - var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType); + var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType); try { media.DisableChangeTracking(); - media.Id = _id; + media.Id = dto.NodeId; media.Key = dto.ContentDto.NodeDto.UniqueId; media.Path = dto.ContentDto.NodeDto.Path; media.CreatorId = dto.ContentDto.NodeDto.UserId.Value; @@ -55,6 +55,13 @@ namespace Umbraco.Core.Persistence.Factories { media.EnableChangeTracking(); } + + } + + [Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")] + public IMedia BuildEntity(ContentVersionDto dto) + { + return BuildEntity(dto, _contentType); } public ContentVersionDto BuildDto(IMedia entity) diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs index 446bd426ad..f202d8c321 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyFactory.cs @@ -30,11 +30,11 @@ namespace Umbraco.Core.Persistence.Factories _updateDate = updateDate; } - public IEnumerable BuildEntity(PropertyDataDto[] dtos) + public static IEnumerable BuildEntity(IReadOnlyCollection dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate) { var properties = new List(); - foreach (var propertyType in _compositionTypeProperties) + foreach (var propertyType in compositionTypeProperties) { var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id); var property = propertyDataDto == null @@ -47,8 +47,8 @@ namespace Umbraco.Core.Persistence.Factories //on initial construction we don't want to have dirty properties tracked property.DisableChangeTracking(); - property.CreateDate = _createDate; - property.UpdateDate = _updateDate; + property.CreateDate = createDate; + property.UpdateDate = updateDate; // http://issues.umbraco.org/issue/U4-1946 property.ResetDirtyProperties(false); properties.Add(property); @@ -57,12 +57,18 @@ namespace Umbraco.Core.Persistence.Factories { property.EnableChangeTracking(); } - + } return properties; } + [Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")] + public IEnumerable BuildEntity(PropertyDataDto[] dtos) + { + return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate); + } + public IEnumerable BuildDto(IEnumerable properties) { var propertyDataDtos = new List(); diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 294f869c3f..b25db8afb2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false) .Where(GetBaseWhereClause(), new { Id = id }) - .Where(x => x.Newest) + .Where(x => x.Newest, SqlSyntax) .OrderByDescending(x => x.VersionDate, SqlSyntax); var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); @@ -77,7 +77,7 @@ namespace Umbraco.Core.Persistence.Repositories } //we only want the newest ones with this method - sql.Where(x => x.Newest); + sql.Where(x => x.Newest, SqlSyntax); return ProcessQuery(sql); } @@ -87,9 +87,9 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .Where(x => x.Newest) - .OrderByDescending(x => x.VersionDate) - .OrderBy(x => x.SortOrder); + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax) + .OrderBy(x => x.SortOrder, SqlSyntax); return ProcessQuery(sql); } @@ -183,8 +183,8 @@ namespace Umbraco.Core.Persistence.Repositories query = query .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); query = query - .Where(x => x.NodeId > baseId && x.Trashed == false) - .Where(x => x.Published) + .Where(x => x.NodeId > baseId && x.Trashed == false, SqlSyntax) + .Where(x => x.Published, SqlSyntax) .OrderBy(x => x.NodeId, SqlSyntax); var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) @@ -222,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); var dto = Database.Fetch(sql).FirstOrDefault(); @@ -238,10 +238,10 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql() .Select("*") - .From() - .InnerJoin().On(left => left.VersionId, right => right.VersionId) - .Where(x => x.VersionId == versionId) - .Where(x => x.Newest != true); + .From(SqlSyntax) + .InnerJoin(SqlSyntax).On(SqlSyntax, left => left.VersionId, right => right.VersionId) + .Where(x => x.VersionId == versionId, SqlSyntax) + .Where(x => x.Newest != true, SqlSyntax); var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return; @@ -848,7 +848,12 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", var content = new IContent[dtos.Count]; var defs = new List(); var templateIds = new List(); - + + //track the looked up content types, even though the content types are cached + // they still need to be deep cloned out of the cache and we don't want to add + // the overhead of deep cloning them on every item in this loop + var contentTypes = new Dictionary(); + for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; @@ -867,9 +872,19 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", // else, need to fetch from the database // content type repository is full-cache so OK to get each one independently - var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - content[i] = factory.BuildEntity(dto); + + IContentType contentType; + if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId)) + { + contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId]; + } + else + { + contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); + contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType; + } + + content[i] = ContentFactory.BuildEntity(dto, contentType); // need template if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) @@ -910,7 +925,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - ((Entity) cc).ResetDirtyProperties(false); + cc.ResetDirtyProperties(false); } return content; @@ -927,8 +942,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", { var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId); - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); + var content = ContentFactory.BuildEntity(dto, contentType); //Check if template id is set on DocumentDto, and get ITemplate if it is. if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 2687848fa5..bf034bd8ff 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -76,7 +76,7 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate() - .OrderBy(x => x.SortOrder); + .OrderBy(x => x.SortOrder, SqlSyntax); return ProcessQuery(sql); } @@ -89,12 +89,12 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = new Sql(); sql.Select(isCount ? "COUNT(*)" : "*") - .From() - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .InnerJoin() - .On(left => left.NodeId, right => right.NodeId) - .Where(x => x.NodeObjectType == NodeObjectTypeId); + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax) + .Where(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax); return sql; } @@ -148,6 +148,11 @@ namespace Umbraco.Core.Persistence.Repositories var content = new IMedia[dtos.Count]; var defs = new List(); + //track the looked up content types, even though the content types are cached + // they still need to be deep cloned out of the cache and we don't want to add + // the overhead of deep cloning them on every item in this loop + var contentTypes = new Dictionary(); + for (var i = 0; i < dtos.Count; i++) { var dto = dtos[i]; @@ -165,9 +170,19 @@ namespace Umbraco.Core.Persistence.Repositories // else, need to fetch from the database // content type repository is full-cache so OK to get each one independently - var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - content[i] = factory.BuildEntity(dto); + + IMediaType contentType; + if (contentTypes.ContainsKey(dto.ContentDto.ContentTypeId)) + { + contentType = contentTypes[dto.ContentDto.ContentTypeId]; + } + else + { + contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + contentTypes[dto.ContentDto.ContentTypeId] = contentType; + } + + content[i] = MediaFactory.BuildEntity(dto, contentType); // need properties defs.Add(new DocumentDefinition( @@ -195,7 +210,7 @@ namespace Umbraco.Core.Persistence.Repositories //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 - ((Entity) cc).ResetDirtyProperties(false); + cc.ResetDirtyProperties(false); } return content; @@ -205,26 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories { var sql = GetBaseQuery(false); sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId }); - sql.OrderByDescending(x => x.VersionDate); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); var dto = Database.Fetch(sql).FirstOrDefault(); if (dto == null) return null; - var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); + var content = CreateMediaFromDto(dto, versionId, sql); - var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) }); - - media.Properties = properties[dto.NodeId]; - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)media).ResetDirtyProperties(false); - return media; + return content; } public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) @@ -245,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories query = query .WhereIn(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax); query = query - .Where(x => x.NodeId > baseId) + .Where(x => x.NodeId > baseId, SqlSyntax) .OrderBy(x => x.NodeId, SqlSyntax); var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize)) .Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() }) @@ -512,9 +517,8 @@ namespace Umbraco.Core.Persistence.Repositories private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql) { var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); + + var media = MediaFactory.BuildEntity(dto, contentType); var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType); diff --git a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs index 19f4b8fdfb..16bad74612 100644 --- a/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/VersionableRepositoryBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Text; @@ -405,11 +406,15 @@ namespace Umbraco.Core.Persistence.Repositories } } - //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. - // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column - // is empty for many nodes) - // see: http://issues.umbraco.org/issue/U4-8831 - sortedSql.OrderBy("umbracoNode.id"); + if (orderBySystemField && orderBy != "umbracoNode.id") + { + //no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique. + // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column + // is empty for many nodes) + // see: http://issues.umbraco.org/issue/U4-8831 + sortedSql.OrderBy("umbracoNode.id"); + } + return sortedSql; @@ -511,9 +516,9 @@ namespace Umbraco.Core.Persistence.Repositories protected IDictionary GetPropertyCollection( Sql docSql, - IEnumerable documentDefs) + IReadOnlyCollection documentDefs) { - if (documentDefs.Any() == false) return new Dictionary(); + if (documentDefs.Count == 0) return new Dictionary(); //we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use // the statement to go get the property data for all of the items by using an inner join @@ -524,6 +529,15 @@ namespace Umbraco.Core.Persistence.Repositories parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal)); } + //It's Important with the sort order here! We require this to be sorted by node id, + // this is required because this data set can be huge depending on the page size. Due + // to it's size we need to be smart about iterating over the property values to build + // the document. Before we used to use Linq to get the property data for a given content node + // and perform a Distinct() call. This kills performance because that would mean if we had 7000 nodes + // and on each iteration we will perform a lookup on potentially 100,000 property rows against the node + // id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll + // keep an index stored of the rows being read so we never have to re-iterate the entire data set + // on each document iteration. var propSql = new Sql(@"SELECT cmsPropertyData.* FROM cmsPropertyData INNER JOIN cmsPropertyType @@ -531,8 +545,8 @@ ON cmsPropertyData.propertytypeid = cmsPropertyType.id INNER JOIN (" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId -LEFT OUTER JOIN cmsDataTypePreValues -ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments); +ORDER BY contentNodeId, propertytypeid +", docSql.Arguments); var allPropertyData = Database.Fetch(propSql); @@ -556,59 +570,81 @@ WHERE EXISTS( }); var result = new Dictionary(); - var propertiesWithTagSupport = new Dictionary(); + //used to track the resolved composition property types per content type so we don't have to re-resolve (ToArray) the list every time + var resolvedCompositionProperties = new Dictionary(); + var propertyDataSetIndex = 0; - //iterate each definition grouped by it's content type - this will mean less property type iterations while building - // up the property collections - foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition)) + //This must be sorted by node id because this is how we are sorting the query to lookup property types above, + // which allows us to more efficiently iterate over the large data set of property values + foreach (var def in documentDefs.OrderBy(x => x.Id)) { - var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray(); - - foreach (var def in compositionGroup) + //get the resolved proeprties from our local cache, or resolve them and put them in cache + PropertyType[] compositionProperties; + if (resolvedCompositionProperties.ContainsKey(def.Composition.Id)) { - var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct(); - - var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate); - var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray(); - - foreach (var property in properties) - { - //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck - var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); - - var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) - ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] - : TagExtractor.GetAttribute(editor); - - if (tagSupport != null) - { - //add to local cache so we don't need to reflect next time for this property editor alias - propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; - - //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up - var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) - .Distinct() - .ToArray(); - - var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); - - var preVals = new PreValueCollection(asDictionary); - - var contentPropData = new ContentPropertyData(property.Value, - preVals, - new Dictionary()); - - TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); - } - } - - if (result.ContainsKey(def.Id)) - { - Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); - } - result[def.Id] = new PropertyCollection(properties); + compositionProperties = resolvedCompositionProperties[def.Composition.Id]; } + else + { + compositionProperties = def.Composition.CompositionPropertyTypes.ToArray(); + resolvedCompositionProperties[def.Composition.Id] = compositionProperties; + } + + var propertyDataDtos = new List(); + + for (var i = propertyDataSetIndex; i < allPropertyData.Count; i++) + { + if (allPropertyData[i].NodeId == def.Id) + { + propertyDataDtos.Add(allPropertyData[i]); + } + else + { + //the node id has changed so we need to exit the loop and store the index + propertyDataSetIndex = i; + break; + } + } + + var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray(); + + foreach (var property in properties) + { + //NOTE: The benchmarks run with and without the following code show very little change so this is not a perf bottleneck + var editor = PropertyEditorResolver.Current.GetByAlias(property.PropertyType.PropertyEditorAlias); + + var tagSupport = propertiesWithTagSupport.ContainsKey(property.PropertyType.PropertyEditorAlias) + ? propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] + : TagExtractor.GetAttribute(editor); + + if (tagSupport != null) + { + //add to local cache so we don't need to reflect next time for this property editor alias + propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport; + + //this property has tags, so we need to extract them and for that we need the prevals which we've already looked up + var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId) + .Distinct() + .ToArray(); + + var asDictionary = preValData.ToDictionary(x => x.Alias, x => new PreValue(x.Id, x.Value, x.SortOrder)); + + var preVals = new PreValueCollection(asDictionary); + + var contentPropData = new ContentPropertyData(property.Value, + preVals, + new Dictionary()); + + TagExtractor.SetPropertyTags(property, contentPropData, property.Value, tagSupport); + } + } + + if (result.ContainsKey(def.Id)) + { + Logger.Warn>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name); + } + result[def.Id] = new PropertyCollection(properties); } return result; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 6f2007912f..d5658b9f9a 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -585,7 +585,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -604,7 +604,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); } diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 7722bf9c65..6d73546571 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -259,7 +259,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -273,7 +273,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index d25ddf7f58..a02ac43b93 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -169,7 +169,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id @@ -183,7 +183,7 @@ namespace Umbraco.Core.Services /// Search text filter /// An Enumerable list of objects IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = ""); + string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = ""); /// /// Gets a collection of objects by Parent Id diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 9a3a30c8bd..f4e963c658 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -451,7 +451,7 @@ namespace Umbraco.Core.Services [Obsolete("Use the overload with 'long' parameter types instead")] [EditorBrowsable(EditorBrowsableState.Never)] - public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { long total; var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter); @@ -470,7 +470,7 @@ namespace Umbraco.Core.Services /// Direction to order by /// Search text filter /// An Enumerable list of objects - public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "") + public IEnumerable GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "umbracoNode.id", Direction orderDirection = Direction.Ascending, string filter = "") { return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter); }