diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 50df3183e1..79b3ce3871 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -703,7 +703,6 @@ namespace Umbraco.Core.Persistence static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); - static Regex rxGroupBy = new Regex(@"\bGROUP\s+BY\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?" (end) - m = rxGroupBy.Match(sqlCount); - if (m.Success != false) - { - g = m.Groups[0]; - sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length); - } - return true; } @@ -828,7 +818,7 @@ namespace Umbraco.Core.Persistence result.CurrentPage = page; result.ItemsPerPage = itemsPerPage; result.TotalItems = ExecuteScalar(sqlCount, args); - result.TotalPages = result.TotalItems / itemsPerPage; + result.TotalPages = result.TotalItems / itemsPerPage; if ((result.TotalItems % itemsPerPage) != 0) result.TotalPages++; diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index ad421f835c..ce4f6034ea 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -55,23 +55,20 @@ namespace Umbraco.Core.Persistence.Repositories { bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media); - - var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); - - var translator = new SqlTranslator(sqlClause, query); - var entitySql = translator.Translate(); - var factory = new UmbracoEntityFactory(); - //use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData + var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId); + var translator = new SqlTranslator(sqlClause, query); + var entitySql = translator.Translate(); var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id"); + IEnumerable result; + if (isMedia) { //Treat media differently for now, as an Entity it will be returned with ALL of it's properties in the AdditionalData bag! var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - totalRecords = pagedResult.TotalItems; - + var ids = pagedResult.Items.Select(x => (int) x.id).InGroupsOf(2000); var entities = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); @@ -128,16 +125,29 @@ namespace Umbraco.Core.Persistence.Repositories propertyDataSetEnumerator.Dispose(); } } - - totalRecords = pagedResult.TotalItems; - return entities; + + result = entities; } else { var pagedResult = _work.Database.Page(pageIndex + 1, pageSize, pagedSql); - totalRecords = pagedResult.TotalItems; - return pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); + result = pagedResult.Items.Select(factory.BuildEntityFromDynamic).Cast().ToList(); } + + //The total items from the PetaPoco page query will be wrong due to the Outer join used on parent, depending on the search this will + //return duplicate results when the COUNT is used in conjuction with it, so we need to get the total on our own. + + //generate a query that does not contain the LEFT Join for parent, this would cause + //the COUNT(*) query to return the wrong + var sqlCountClause = GetBaseWhere( + (isC, isM, f) => GetBase(isC, isM, f, true), //true == is a count query + isContent, isMedia, null, objectTypeId); + var translatorCount = new SqlTranslator(sqlCountClause, query); + var countSql = translatorCount.Translate(); + + totalRecords = _work.Database.ExecuteScalar(countSql); + + return result; } public IUmbracoEntity GetByKey(Guid key) @@ -404,39 +414,52 @@ namespace Umbraco.Core.Persistence.Repositories .OrderBy("sortOrder, id"); return wrappedSql; - } + } protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter) { - var columns = new List - { - "umbracoNode.id", - "umbracoNode.trashed", - "umbracoNode.parentID", - "umbracoNode.nodeUser", - "umbracoNode.level", - "umbracoNode.path", - "umbracoNode.sortOrder", - "umbracoNode.uniqueID", - "umbracoNode.text", - "umbracoNode.nodeObjectType", - "umbracoNode.createDate", - "COUNT(parent.parentID) as children" - }; + return GetBase(isContent, isMedia, customFilter, false); + } - if (isContent || isMedia) + protected virtual Sql GetBase(bool isContent, bool isMedia, Action customFilter, bool isCount) + { + var columns = new List(); + if (isCount) { - if (isContent) + columns.Add("COUNT(*)"); + } + else + { + columns.AddRange(new List { - //only content has this info - columns.Add("published.versionId as publishedVersion"); - columns.Add("document.versionId as newestVersion"); + "umbracoNode.id", + "umbracoNode.trashed", + "umbracoNode.parentID", + "umbracoNode.nodeUser", + "umbracoNode.level", + "umbracoNode.path", + "umbracoNode.sortOrder", + "umbracoNode.uniqueID", + "umbracoNode.text", + "umbracoNode.nodeObjectType", + "umbracoNode.createDate", + "COUNT(parent.parentID) as children" + }); + + if (isContent || isMedia) + { + if (isContent) + { + //only content has this info + columns.Add("published.versionId as publishedVersion"); + columns.Add("document.versionId as newestVersion"); + } + + columns.Add("contenttype.alias"); + columns.Add("contenttype.icon"); + columns.Add("contenttype.thumbnail"); + columns.Add("contenttype.isContainer"); } - - columns.Add("contenttype.alias"); - columns.Add("contenttype.icon"); - columns.Add("contenttype.thumbnail"); - columns.Add("contenttype.isContainer"); } //Creates an SQL query to return a single row for the entity @@ -461,7 +484,10 @@ namespace Umbraco.Core.Persistence.Repositories entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType"); } - entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); + if (isCount == false) + { + entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id"); + } if (customFilter != null) { diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index c18e62cd28..e3bfb6dde9 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -396,6 +396,33 @@ namespace Umbraco.Core.Services } } + public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var uow = UowProvider.GetUnitOfWork()) + { + var repository = RepositoryFactory.CreateEntityRepository(uow); + + var query = Query.Builder; + //if the id is System Root, then just get all + if (id != Constants.System.Root) + { + query.Where(x => x.Path.SqlContains(string.Format(",{0},", id), TextColumnType.NVarchar)); + } + + IQuery filterQuery = null; + if (filter.IsNullOrWhiteSpace() == false) + { + filterQuery = Query.Builder.Where(x => x.Name.Contains(filter)); + } + + var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + uow.Commit(); + return contents; + } + } + /// /// Gets a collection of descendents by the parents Id /// diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index d9052f4bb8..339b0b7146 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -141,6 +141,9 @@ namespace Umbraco.Core.Services IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, + string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + /// /// Gets a collection of descendents by the parents Id /// diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index eb2eccb94d..81d4cad6c0 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -52,6 +52,39 @@ namespace Umbraco.Tests.Services Assert.That(total, Is.EqualTo(10)); } + [Test] + public void EntityService_Can_Get_Paged_Content_Descendants() + { + var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var count = 0; + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + count++; + + for (int j = 0; j < 5; j++) + { + var c2 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), c1); + ServiceContext.ContentService.Save(c2); + count++; + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(31)); + Assert.That(total, Is.EqualTo(60)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 1, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(29)); + Assert.That(total, Is.EqualTo(60)); + } + [Test] public void EntityService_Can_Get_Paged_Media_Children() { @@ -77,6 +110,40 @@ namespace Umbraco.Tests.Services Assert.That(total, Is.EqualTo(10)); } + [Test] + public void EntityService_Can_Get_Paged_Media_Descendants() + { + var folderType = ServiceContext.ContentTypeService.GetMediaType(1031); + var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032); + + var root = MockedMedia.CreateMediaFolder(folderType, -1); + ServiceContext.MediaService.Save(root); + var count = 0; + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageMediaType, root.Id); + ServiceContext.MediaService.Save(c1); + count++; + + for (int j = 0; j < 5; j++) + { + var c2 = MockedMedia.CreateMediaImage(imageMediaType, c1.Id); + ServiceContext.MediaService.Save(c2); + count++; + } + } + + var service = ServiceContext.EntityService; + + long total; + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(31)); + Assert.That(total, Is.EqualTo(60)); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 1, 31, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(29)); + Assert.That(total, Is.EqualTo(60)); + } + [Test] public void EntityService_Can_Find_All_Content_By_UmbracoObjectTypes() {