diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index fbb68194b7..2ea0fd855b 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -108,6 +108,9 @@ namespace Umbraco.Core.Models [IgnoreDataMember] public int VersionId { get; set; } + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// /// Integer Id of the default ContentType /// diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 04bcb7424a..1f3f1a5c13 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -113,6 +113,9 @@ namespace Umbraco.Core.Models OnPropertyChanged(nameof(PropertyTypes)); } + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// /// The Alias of the ContentType /// diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index c237f6381c..cba53bbd10 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -65,6 +65,9 @@ namespace Umbraco.Core.Models [DataMember] public string EditorAlias => _editor.Alias; + [DataMember] + public Guid NodeObjectType { get; internal set; } + /// [DataMember] public ValueStorageType DatabaseType diff --git a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs index afa3399202..ab63e1e1d8 100644 --- a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs +++ b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs @@ -24,7 +24,7 @@ /// Sets the parent entity. /// /// Use this method to set the parent entity when the parent entity is known, but has not - /// been persistent and does not yet have an identity. The parent identifier will we retrieved + /// been persistent and does not yet have an identity. The parent identifier will be retrieved /// from the parent entity when needed. If the parent entity still does not have an entity by that /// time, an exception will be thrown by getter. void SetParent(ITreeEntity parent); @@ -53,4 +53,4 @@ /// bool Trashed { get; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs index e5f628b098..13087b5e95 100644 --- a/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs +++ b/src/Umbraco.Core/Models/Entities/IUmbracoEntity.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Models.Entities { @@ -11,5 +12,7 @@ namespace Umbraco.Core.Models.Entities /// An IUmbracoEntity can participate in notifications. /// public interface IUmbracoEntity : ITreeEntity, IRememberBeingDirty - { } + { + Guid NodeObjectType { get; } + } } diff --git a/src/Umbraco.Core/Models/EntityContainer.cs b/src/Umbraco.Core/Models/EntityContainer.cs index 70f6cbd878..71a477a9d6 100644 --- a/src/Umbraco.Core/Models/EntityContainer.cs +++ b/src/Umbraco.Core/Models/EntityContainer.cs @@ -62,6 +62,8 @@ namespace Umbraco.Core.Models /// public Guid ContainerObjectType => ObjectTypeMap[_containedObjectType]; + Guid IUmbracoEntity.NodeObjectType => ContainerObjectType; + /// /// Gets the container object type corresponding to a contained object type. /// diff --git a/src/Umbraco.Core/Models/SimpleContentType.cs b/src/Umbraco.Core/Models/SimpleContentType.cs index 5c81017ec8..b4e7688eea 100644 --- a/src/Umbraco.Core/Models/SimpleContentType.cs +++ b/src/Umbraco.Core/Models/SimpleContentType.cs @@ -44,11 +44,14 @@ namespace Umbraco.Core.Models Name = contentType.Name; AllowedAsRoot = contentType.AllowedAsRoot; IsElement = contentType.IsElement; + NodeObjectType = contentType.NodeObjectType; } /// public string Alias { get; } + public Guid NodeObjectType { get; } + public int Id { get; } /// diff --git a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs index 434e0393cd..25cc6bc358 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentBaseFactory.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Factories content.VersionId = contentVersionDto.Id; content.Name = contentVersionDto.Text; - + content.NodeObjectType = nodeDto.NodeObjectType ?? Guid.Empty; content.Path = nodeDto.Path; content.Level = nodeDto.Level; content.ParentId = nodeDto.ParentId; diff --git a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs index 54cfee0ffa..46489aa69e 100644 --- a/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/ContentTypeFactory.cs @@ -107,6 +107,7 @@ namespace Umbraco.Core.Persistence.Factories { entity.Id = dto.NodeDto.NodeId; entity.Key = dto.NodeDto.UniqueId; + entity.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; entity.Alias = dto.Alias; entity.Name = dto.NodeDto.Text; entity.Icon = dto.Icon; diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index f189d38d05..5d92234ca8 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -28,6 +28,7 @@ namespace Umbraco.Core.Persistence.Factories dataType.DisableChangeTracking(); dataType.CreateDate = dto.NodeDto.CreateDate; + dataType.NodeObjectType = dto.NodeDto.NodeObjectType ?? Guid.Empty; dataType.DatabaseType = dto.DbType.EnumParse(true); dataType.Id = dto.NodeId; dataType.Key = dto.NodeDto.UniqueId; diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs index 16e2e8bcd5..96f664cc25 100644 --- a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -24,6 +24,7 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(IUmbracoEntity.Trashed), nameof(NodeDto.Trashed)); DefineMap(nameof(IUmbracoEntity.Key), nameof(NodeDto.UniqueId)); DefineMap(nameof(IUmbracoEntity.CreatorId), nameof(NodeDto.UserId)); + DefineMap(nameof(IUmbracoEntity.NodeObjectType), nameof(NodeDto.NodeObjectType)); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index 69f6ef4c5f..a6a1691347 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -15,10 +15,22 @@ namespace Umbraco.Core.Persistence.Repositories IEntitySlim Get(int id, Guid objectTypeId); IEntitySlim Get(Guid key, Guid objectTypeId); - IEnumerable GetAll(Guid objectType, params int[] ids); + IEnumerable GetAll(Guid objectType, params int[] ids); IEnumerable GetAll(Guid objectType, params Guid[] keys); + /// + /// Gets entities for a query + /// + /// + /// IEnumerable GetByQuery(IQuery query); + + /// + /// Gets entities for a query and a specific object type allowing the query to be slightly more optimized + /// + /// + /// + /// IEnumerable GetByQuery(IQuery query, Guid objectType); UmbracoObjectTypes GetObjectType(int id); @@ -30,7 +42,36 @@ namespace Umbraco.Core.Persistence.Repositories bool Exists(int id); bool Exists(Guid key); + /// + /// Gets paged entities for a query and a subset of object types + /// + /// + /// + /// + /// + /// + /// + /// + /// A collection of mixed entity types which would be of type , , , + /// + /// + IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering); + + /// + /// Gets paged entities for a query and a specific object type + /// + /// + /// + /// + /// + /// + /// + /// + /// IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering); + + } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 161db543ba..2b68c8fe19 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -36,26 +36,70 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Repository - // get a page of entities + //public IEnumerable GetPagedResults(IDictionary> typesAndIds, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) + //{ + // var isContent = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); + // var isMedia = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Media); + // var isMember = typesAndIds.Keys.Any(objectType => objectType == Constants.ObjectTypes.Member); + + // var sql = GetBase(isContent, isMedia, isMember, null, false) + // .WhereIn(x => x.NodeObjectType, typesAndIds.Keys); + + // ordering = ordering ?? Ordering.ByDefault(); + + // sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); + + // if (!ordering.IsEmpty) + // { + // // apply ordering + // ApplyOrdering(ref sql, ordering); + // } + + // // TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently + // //no matter what we always must have node id ordered at the end + // sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); + + // // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names + // var pageIndexToFetch = pageIndex + 1; + // IEnumerable dtos; + // var page = Database.Page(pageIndexToFetch, pageSize, sql); + // dtos = page.Items; + // totalRecords = page.TotalItems; + + // var entities = dtos.Select(BuildEntity).ToArray(); + + // BuildVariants(entities.OfType()); + + // return entities; + //} + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, IQuery filter, Ordering ordering) { - var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; - var isMedia = objectType == Constants.ObjectTypes.Media; - var isMember = objectType == Constants.ObjectTypes.Member; + query = query.Where(x => x.NodeObjectType == objectType); + return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering); + } + + // get a page of entities + public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords, + IQuery filter, Ordering ordering) + { + var isContent = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint); + var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media); + var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member); var sql = GetBaseWhere(isContent, isMedia, isMember, false, x => { if (filter == null) return; foreach (var filterClause in filter.GetWhereClauses()) x.Where(filterClause.Item1, filterClause.Item2); - }, objectType); + }, objectTypes); ordering = ordering ?? Ordering.ByDefault(); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty); + sql = AddGroupBy(true, true, true, sql, ordering.IsEmpty); if (!ordering.IsEmpty) { @@ -70,35 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names var pageIndexToFetch = pageIndex + 1; IEnumerable dtos; - if(isContent) - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } - else if (isMedia) - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } - else if (isMember) - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } - else - { - var page = Database.Page(pageIndexToFetch, pageSize, sql); - dtos = page.Items; - totalRecords = page.TotalItems; - } + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; - var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray(); + var entities = dtos.Select(BuildEntity).ToArray(); - if (isContent) - BuildVariants(entities.Cast()); + BuildVariants(entities.OfType()); return entities; } @@ -107,7 +129,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var sql = GetBaseWhere(false, false, false, false, key); var dto = Database.FirstOrDefault(sql); - return dto == null ? null : BuildEntity(false, false, false, dto); + return dto == null ? null : BuildEntity(dto); } @@ -116,7 +138,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { - var cdtos = Database.Fetch(sql); + var cdtos = Database.Fetch(sql); return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0])); } @@ -127,7 +149,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (dto == null) return null; - var entity = BuildEntity(false, isMedia, isMember, dto); + var entity = BuildEntity(dto); return entity; } @@ -146,7 +168,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { var sql = GetBaseWhere(false, false, false, false, id); var dto = Database.FirstOrDefault(sql); - return dto == null ? null : BuildEntity(false, false, false, dto); + return dto == null ? null : BuildEntity(dto); } public IEntitySlim Get(int id, Guid objectTypeId) @@ -178,7 +200,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //isContent is going to return a 1:M result now with the variants so we need to do different things if (isContent) { - var cdtos = Database.Fetch(sql); + var cdtos = Database.Fetch(sql); return cdtos.Count == 0 ? Enumerable.Empty() @@ -189,7 +211,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ? (IEnumerable)Database.Fetch(sql) : Database.Fetch(sql); - var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray(); + var entities = dtos.Select(BuildEntity).ToArray(); return entities; } @@ -233,7 +255,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var sql = translator.Translate(); sql = AddGroupBy(false, false, false, sql, true); var dtos = Database.Fetch(sql); - return dtos.Select(x => BuildEntity(false, false, false, x)).ToList(); + return dtos.Select(BuildEntity).ToList(); } public IEnumerable GetByQuery(IQuery query, Guid objectType) @@ -242,7 +264,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var isMedia = objectType == Constants.ObjectTypes.Media; var isMember = objectType == Constants.ObjectTypes.Member; - var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, new[] { objectType }); var translator = new SqlTranslator(sql, query); sql = translator.Translate(); @@ -356,14 +378,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the full sql for a given object type, with a given filter protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action> filter) { - var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType); + var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType }); return AddGroupBy(isContent, isMedia, isMember, sql, true); } // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version protected Sql GetBase(bool isContent, bool isMedia, bool isMember, Action> filter, bool isCount = false) - { + { var sql = Sql(); if (isCount) @@ -401,15 +423,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (isContent || isMedia || isMember) { sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) - .InnerJoin().On((left, right) => left.NodeId == right.NodeId) - .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId); + .LeftJoin().On((left, right) => left.NodeId == right.NodeId && right.Current) + .LeftJoin().On((left, right) => left.NodeId == right.NodeId) + .LeftJoin().On((left, right) => left.ContentTypeId == right.NodeId); } if (isContent) { sql - .InnerJoin().On((left, right) => left.NodeId == right.NodeId); + .LeftJoin().On((left, right) => left.NodeId == right.NodeId); } if (isMedia) @@ -433,10 +455,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM [+ filter] + WHERE sql // for a given object type, with a given filter - protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid objectType) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid[] objectTypes) { return GetBase(isContent, isMedia, isMember, filter, isCount) - .Where(x => x.NodeObjectType == objectType); + .WhereIn(x => x.NodeObjectType, objectTypes); } // gets the base SELECT + FROM + WHERE sql @@ -510,8 +532,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (sql == null) throw new ArgumentNullException(nameof(sql)); if (ordering == null) throw new ArgumentNullException(nameof(ordering)); - // TODO: although this works for name, it probably doesn't work for others without an alias of some sort - var orderBy = ordering.OrderBy; + // TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort + // As more things are attempted to be sorted we'll prob have to add more expressions here + var orderBy = ordering.OrderBy.ToUpperInvariant() switch + { + "PATH" => SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"), + _ => ordering.OrderBy + }; if (ordering.Direction == Direction.Ascending) sql.OrderBy(orderBy); @@ -524,9 +551,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Classes /// - /// The DTO used to fetch results for a content item with its variation info + /// The DTO used to fetch results for a generic content item which could be either a document, media or a member /// - private class ContentEntityDto : BaseDto + private class GenericContentEntityDto : DocumentEntityDto + { + public string MediaPath { get; set; } + } + + /// + /// The DTO used to fetch results for a document item with its variation info + /// + private class DocumentEntityDto : BaseDto { public ContentVariation Variations { get; set; } @@ -534,11 +569,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement public bool Edited { get; set; } } + /// + /// The DTO used to fetch results for a media item with its media path info + /// private class MediaEntityDto : BaseDto { public string MediaPath { get; set; } } + /// + /// The DTO used to fetch results for a member item + /// private class MemberEntityDto : BaseDto { } @@ -589,13 +630,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement #region Factory - private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto) + private EntitySlim BuildEntity(BaseDto dto) { - if (isContent) + if (dto.NodeObjectType == Constants.ObjectTypes.Document) return BuildDocumentEntity(dto); - if (isMedia) + if (dto.NodeObjectType == Constants.ObjectTypes.Media) return BuildMediaEntity(dto); - if (isMember) + if (dto.NodeObjectType == Constants.ObjectTypes.Member) return BuildMemberEntity(dto); // EntitySlim does not track changes @@ -650,7 +691,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new DocumentEntitySlim(); BuildContentEntity(entity, dto); - if (dto is ContentEntityDto contentDto) + if (dto is DocumentEntityDto contentDto) { // fill in the invariant info entity.Edited = contentDto.Edited; diff --git a/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs new file mode 100644 index 0000000000..a4df5bcf78 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/Repositories/EntityRepositoryTest.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.Scoping; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Tests.Testing; + +namespace Umbraco.Tests.Persistence.Repositories +{ + [TestFixture] + [UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)] + public class EntityRepositoryTest : TestWithDatabaseBase + { + + private EntityRepository CreateRepository(IScopeAccessor scopeAccessor) + { + var entityRepository = new EntityRepository(scopeAccessor); + return entityRepository; + } + + [Test] + public void Get_Paged_Mixed_Entities_By_Ids() + { + //Create content + + var createdContent = new List(); + var contentType = MockedContentTypes.CreateBasicContentType("blah"); + ServiceContext.ContentTypeService.Save(contentType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateBasicContent(contentType); + ServiceContext.ContentService.Save(c1); + createdContent.Add(c1); + } + + //Create media + + var createdMedia = new List(); + var imageType = MockedContentTypes.CreateImageMediaType("myImage"); + ServiceContext.MediaTypeService.Save(imageType); + for (int i = 0; i < 10; i++) + { + var c1 = MockedMedia.CreateMediaImage(imageType, -1); + ServiceContext.MediaService.Save(c1); + createdMedia.Add(c1); + } + + // Create members + var memberType = MockedContentTypes.CreateSimpleMemberType("simple"); + ServiceContext.MemberTypeService.Save(memberType); + var createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList(); + ServiceContext.MemberService.Save(createdMembers); + + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repo = CreateRepository((IScopeAccessor)provider); + + var ids = createdContent.Select(x => x.Id).Concat(createdMedia.Select(x => x.Id)).Concat(createdMembers.Select(x => x.Id)); + + var objectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }; + + var query = SqlContext.Query() + .WhereIn(e => e.Id, ids); + + var entities = repo.GetPagedResultsByQuery(query, objectTypes, 0, 20, out var totalRecords, null, null).ToList(); + + Assert.AreEqual(20, entities.Count); + Assert.AreEqual(30, totalRecords); + + //add the next page + entities.AddRange(repo.GetPagedResultsByQuery(query, objectTypes, 1, 20, out totalRecords, null, null)); + + Assert.AreEqual(30, entities.Count); + Assert.AreEqual(30, totalRecords); + + var contentEntities = entities.OfType().ToList(); + var mediaEntities = entities.OfType().ToList(); + var memberEntities = entities.OfType().ToList(); + + Assert.AreEqual(10, contentEntities.Count); + Assert.AreEqual(10, mediaEntities.Count); + Assert.AreEqual(10, memberEntities.Count); + } + + } + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4b035f631e..c87e6501f9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -140,6 +140,7 @@ + diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 0513017b70..11b1260e71 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -662,6 +662,9 @@ namespace Umbraco.Web.Editors if (pageSize <= 0) throw new HttpResponseException(HttpStatusCode.NotFound); + // re-normalize since NULL can be passed in + filter = filter ?? string.Empty; + var objectType = ConvertToObjectType(type); if (objectType.HasValue) { diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs index 2e44f1327b..afd1a6b61c 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs @@ -226,11 +226,11 @@ namespace Umbraco.Web.Models.Mapping { switch (entity) { - case ContentEntitySlim contentEntity: - // NOTE: this case covers both content and media entities - return contentEntity.ContentTypeIcon; - case MemberEntitySlim memberEntity: + case IMemberEntitySlim memberEntity: return memberEntity.ContentTypeIcon.IfNullOrWhiteSpace(Constants.Icons.Member); + case IContentEntitySlim contentEntity: + // NOTE: this case covers both content and media entities + return contentEntity.ContentTypeIcon; } return null; diff --git a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs index 88960fb189..aa158799cb 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapDefinition.cs @@ -380,8 +380,8 @@ namespace Umbraco.Web.Models.Mapping .ToDictionary(x => x.Key, x => (IEnumerable)x.ToArray()); } - private static string MapContentTypeIcon(EntitySlim entity) - => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + private static string MapContentTypeIcon(IEntitySlim entity) + => entity is IContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; private IEnumerable GetStartNodes(int[] startNodeIds, UmbracoObjectTypes objectType, string localizedKey, MapperContext context) { diff --git a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs index ac75fd831d..fd05f7cfbd 100644 --- a/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentBlueprintTreeController.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Trees if (id == Constants.System.RootString) { //get all blueprint content types - var contentTypeAliases = entities.Select(x => ((ContentEntitySlim) x).ContentTypeAlias).Distinct(); + var contentTypeAliases = entities.Select(x => ((IContentEntitySlim) x).ContentTypeAlias).Distinct(); //get the ids var contentTypeIds = Services.ContentTypeService.GetAllContentTypeIds(contentTypeAliases.ToArray()).ToArray(); @@ -75,7 +75,7 @@ namespace Umbraco.Web.Trees var ct = Services.ContentTypeService.Get(intId.Result); if (ct == null) return nodes; - var blueprintsForDocType = entities.Where(x => ct.Alias == ((ContentEntitySlim) x).ContentTypeAlias); + var blueprintsForDocType = entities.Where(x => ct.Alias == ((IContentEntitySlim) x).ContentTypeAlias); nodes.AddRange(blueprintsForDocType .Select(entity => {