diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index f5823303fd..511d7ce1bc 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Xml.Linq; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { @@ -21,5 +24,18 @@ namespace Umbraco.Core.Persistence.Repositories /// void AddOrUpdatePreviewXml(IMedia content, Func xml); + /// + /// Gets paged media results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedResultsByQuery(IQuery query, int pageNumber, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, string filter = ""); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index a736f16e3c..eea58c2993 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -4,11 +4,13 @@ using System.Globalization; using System.Linq; using System.Xml.Linq; using Umbraco.Core.Configuration; +using Umbraco.Core.Dynamics; using Umbraco.Core.IO; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -397,6 +399,109 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion + + /// + /// Gets paged media results + /// + /// Query to excute + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedResultsByQuery(IQuery query, int pageNumber, int pageSize, out int totalRecords, + string orderBy, Direction orderDirection, string filter = "") + { + // Get base query + var sqlClause = GetBaseQuery(false); + var translator = new SqlTranslator(sqlClause, query); + var sql = translator.Translate(); + + // Apply filter + if (!string.IsNullOrEmpty(filter)) + { + sql = sql.Where("umbracoNode.text LIKE @0", "%" + filter + "%"); + } + + // Apply order according to parameters + if (!string.IsNullOrEmpty(orderBy)) + { + var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) }; + if (orderDirection == Direction.Ascending) + { + sql = sql.OrderBy(orderByParams); + } + else + { + sql = sql.OrderByDescending(orderByParams); + } + } + + // Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery, + // but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id. + // So we'll modify the SQL. + var modifiedSQL = sql.SQL.Replace("SELECT *", "SELECT cmsContentVersion.contentId"); + + // Get page of results and total count + IEnumerable result; + var pagedResult = Database.Page(pageNumber, pageSize, modifiedSQL, sql.Arguments); + totalRecords = Convert.ToInt32(pagedResult.TotalItems); + if (totalRecords > 0) + { + // Parse out node Ids and load media (we need the cast here in order to be able to call the IQueryable extension + // methods OrderBy or OrderByDescending) + var media = GetAll(pagedResult.Items + .DistinctBy(x => x.NodeId) + .Select(x => x.NodeId).ToArray()) + .Cast() + .AsQueryable(); + + // Now we need to ensure this result is also ordered by the same order by clause + var orderByProperty = GetIMediaPropertyNameForOrderBy(orderBy); + if (orderDirection == Direction.Ascending) + { + result = media.OrderBy(orderByProperty); + } + else + { + result = media.OrderByDescending(orderByProperty); + } + } + else + { + result = Enumerable.Empty(); + } + + return result; + } + + private string GetDatabaseFieldNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the database field names. + switch (orderBy) + { + case "Name": + return "umbracoNode.text"; + default: + return "umbracoNode.sortOrder"; + } + } + + private string GetIMediaPropertyNameForOrderBy(string orderBy) + { + // Translate the passed order by field (which were originally defined for in-memory object sorting + // of ContentItemBasic instances) to the IMedia property names. + switch (orderBy) + { + case "Name": + return "Name"; + default: + return "SortOrder"; + } + } private string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0) { diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index a457f69502..9916b77905 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Services { @@ -61,6 +62,20 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects IEnumerable GetChildren(int id); + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + IEnumerable GetPagedChildren(int id, int pageNumber, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, string filter = ""); + /// /// Gets descendants of a object by its Id /// diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 518be0ef07..e4270253d0 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; @@ -394,6 +395,31 @@ namespace Umbraco.Core.Services } } + /// + /// Gets a collection of objects by Parent Id + /// + /// Id of the Parent to retrieve Children from + /// Page number + /// Page size + /// Total records query would return without paging + /// Field to order by + /// Direction to order by + /// Search text filter + /// An Enumerable list of objects + public IEnumerable GetPagedChildren(int id, int pageNumber, int pageSize, out int totalChildren, + string orderBy, Direction orderDirection, string filter = "") + { + Mandate.ParameterCondition(pageNumber > 0, "pageSize"); + Mandate.ParameterCondition(pageSize > 0, "pageSize"); + using (var repository = _repositoryFactory.CreateMediaRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var medias = repository.GetPagedResultsByQuery(query, pageNumber, pageSize, out totalChildren, orderBy, orderDirection, filter); + + return medias; + } + } + /// /// Gets descendants of a object by its Id /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index 8f31023bdf..95890bdf8b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -14,6 +14,7 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; using umbraco.editorControls.tinyMCE3; using umbraco.interfaces; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Persistence.Repositories { @@ -240,7 +241,6 @@ namespace Umbraco.Tests.Persistence.Repositories MediaTypeRepository mediaTypeRepository; using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) { - // Act var query = Query.Builder.Where(x => x.Level == 2); var result = repository.GetByQuery(query); @@ -250,6 +250,153 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 2, 1, out totalRecords, "SortOrder", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "SortOrder", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(2)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Descending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WitAlternateOrder_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending); + + // Assert + Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, "File"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(1)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test File")); + } + } + + [Test] + public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository() + { + // Arrange + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository)) + { + // Act + var query = Query.Builder.Where(x => x.Level == 2); + int totalRecords; + var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "SortOrder", Direction.Ascending, "Test"); + + // Assert + Assert.That(totalRecords, Is.EqualTo(2)); + Assert.That(result.Count(), Is.EqualTo(1)); + Assert.That(result.First().Name, Is.EqualTo("Test Image")); + } + } + [Test] public void Can_Perform_GetAll_By_Param_Ids_On_MediaRepository() { diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 3ea57a7967..84eedb000c 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -138,48 +138,28 @@ namespace Umbraco.Web.Editors Direction orderDirection = Direction.Ascending, string filter = "") { - //TODO: This will be horribly inefficient for paging! This is because our datasource/repository - // doesn't support paging at the SQL level... and it'll be pretty interesting to try to make that work. - - var children = Services.MediaService.GetChildren(id).ToArray(); - var totalChildren = children.Length; - - if (totalChildren == 0) - return new PagedResult>(0, 0, 0); - - var result = children - .Select(Mapper.Map>) - .AsQueryable(); - - //TODO: This is a rudimentry filter - should use the logic found in the EntityService filter (dynamic linq) instead - if (!string.IsNullOrEmpty(filter)) - { - filter = filter.ToLower(); - result = result.Where(x => x.Name.InvariantContains(filter)); - } - - var orderedResult = orderDirection == Direction.Ascending - ? result.OrderBy(orderBy) - : result.OrderByDescending(orderBy); - - var pagedResult = new PagedResult>( - totalChildren, - pageNumber, - pageSize); - + int totalChildren; + IMedia[] children; if (pageNumber > 0 && pageSize > 0) { - pagedResult.Items = orderedResult - .Skip(pagedResult.SkipSize) - .Take(pageSize); + children = Services.MediaService.GetPagedChildren(id, pageNumber, pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray(); } else { - pagedResult.Items = orderedResult; + children = Services.MediaService.GetChildren(id).ToArray(); + totalChildren = children.Length; } - return pagedResult; + if (totalChildren == 0) + { + return new PagedResult>(0, 0, 0); + } + var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); + pagedResult.Items = children + .Select(Mapper.Map>); + + return pagedResult; } ///