diff --git a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs index 5867f8495d..6c69e16edf 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs @@ -2,11 +2,14 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { public interface IRelationRepository : IReadWriteQueryRepository { + IEnumerable GetPagedRelationsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering); + /// /// Persist multiple at once /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 3ca28b6b44..a0d2baa907 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -516,11 +516,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // 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 + string orderBy; + switch (ordering.OrderBy.ToUpperInvariant()) { - "PATH" => SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"), - _ => ordering.OrderBy - }; + case "PATH": + orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path"); + break; + + default: + orderBy = ordering.OrderBy; + break; + } if (ordering.Direction == Direction.Ascending) sql.OrderBy(orderBy); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs index 34170d7dd4..68575b2bab 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -239,6 +240,38 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } + public IEnumerable GetPagedRelationsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering) + { + var sql = GetBaseQuery(false); + + if (ordering == null || ordering.IsEmpty) + ordering = Ordering.By(SqlSyntax.GetQuotedColumn(Constants.DatabaseSchema.Tables.Relation, "id")); + + var translator = new SqlTranslator(sql, query); + sql = translator.Translate(); + + // apply ordering + ApplyOrdering(ref sql, ordering); + + var pageIndexToFetch = pageIndex + 1; + var page = Database.Page(pageIndexToFetch, pageSize, sql); + var dtos = page.Items; + totalRecords = page.TotalItems; + + var relTypes = _relationTypeRepository.GetMany(dtos.Select(x => x.RelationType).Distinct().ToArray()) + .ToDictionary(x => x.Id, x => x); + + var result = dtos.Select(r => + { + if (!relTypes.TryGetValue(r.RelationType, out var relType)) + throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", r.RelationType)); + return DtoToEntity(r, relType); + }).ToList(); + + return result; + } + + public void DeleteByParent(int parentId, params string[] relationTypeAliases) { var subQuery = Sql().Select(x => x.Id) @@ -278,5 +311,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } } } + + private void ApplyOrdering(ref Sql sql, Ordering ordering) + { + 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; + + if (ordering.Direction == Direction.Ascending) + sql.OrderBy(orderBy); + else + sql.OrderByDescending(orderBy); + } } } diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs index 0ce28901cd..4d94ddb48c 100644 --- a/src/Umbraco.Core/Services/IRelationService.cs +++ b/src/Umbraco.Core/Services/IRelationService.cs @@ -8,152 +8,162 @@ namespace Umbraco.Core.Services public interface IRelationService : IService { /// - /// Gets a by its Id + /// Gets a by its Id /// - /// Id of the - /// A object + /// Id of the + /// A object IRelation GetById(int id); /// - /// Gets a by its Id + /// Gets a by its Id /// - /// Id of the - /// A object + /// Id of the + /// A object IRelationType GetRelationTypeById(int id); /// - /// Gets a by its Id + /// Gets a by its Id /// - /// Id of the - /// A object + /// Id of the + /// A object IRelationType GetRelationTypeById(Guid id); /// - /// Gets a by its Alias + /// Gets a by its Alias /// - /// Alias of the - /// A object + /// Alias of the + /// A object IRelationType GetRelationTypeByAlias(string alias); /// - /// Gets all objects + /// Gets all objects /// /// Optional array of integer ids to return relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetAllRelations(params int[] ids); /// - /// Gets all objects by their + /// Gets all objects by their /// - /// to retrieve Relations for - /// An enumerable list of objects + /// to retrieve Relations for + /// An enumerable list of objects IEnumerable GetAllRelationsByRelationType(IRelationType relationType); /// - /// Gets all objects by their 's Id + /// Gets all objects by their 's Id /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects + /// Id of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetAllRelationsByRelationType(int relationTypeId); /// - /// Gets all objects + /// Gets all objects /// /// Optional array of integer ids to return relationtypes for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetAllRelationTypes(params int[] ids); /// - /// Gets a list of objects by their parent Id + /// Gets a list of objects by their parent Id /// /// Id of the parent to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParentId(int id); /// - /// Gets a list of objects by their parent Id + /// Gets a list of objects by their parent Id /// /// Id of the parent to retrieve relations for /// Alias of the type of relation to retrieve - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParentId(int id, string relationTypeAlias); /// - /// Gets a list of objects by their parent entity + /// Gets a list of objects by their parent entity /// /// Parent Entity to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParent(IUmbracoEntity parent); /// - /// Gets a list of objects by their parent entity + /// Gets a list of objects by their parent entity /// /// Parent Entity to retrieve relations for /// Alias of the type of relation to retrieve - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias); /// - /// Gets a list of objects by their child Id + /// Gets a list of objects by their child Id /// /// Id of the child to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChildId(int id); /// - /// Gets a list of objects by their child Id + /// Gets a list of objects by their child Id /// /// Id of the child to retrieve relations for /// Alias of the type of relation to retrieve - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChildId(int id, string relationTypeAlias); /// - /// Gets a list of objects by their child Entity + /// Gets a list of objects by their child Entity /// /// Child Entity to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChild(IUmbracoEntity child); /// - /// Gets a list of objects by their child Entity + /// Gets a list of objects by their child Entity /// /// Child Entity to retrieve relations for /// Alias of the type of relation to retrieve - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias); /// - /// Gets a list of objects by their child or parent Id. + /// Gets a list of objects by their child or parent Id. /// Using this method will get you all relations regards of it being a child or parent relation. /// /// Id of the child or parent to retrieve relations for - /// An enumerable list of objects + /// An enumerable list of objects IEnumerable GetByParentOrChildId(int id); IEnumerable GetByParentOrChildId(int id, string relationTypeAlias); /// - /// Gets a list of objects by the Name of the + /// Gets a list of objects by the Name of the /// - /// Name of the to retrieve Relations for - /// An enumerable list of objects + /// Name of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetByRelationTypeName(string relationTypeName); /// - /// Gets a list of objects by the Alias of the + /// Gets a list of objects by the Alias of the /// - /// Alias of the to retrieve Relations for - /// An enumerable list of objects + /// Alias of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetByRelationTypeAlias(string relationTypeAlias); /// - /// Gets a list of objects by the Id of the + /// Gets a list of objects by the Id of the /// - /// Id of the to retrieve Relations for - /// An enumerable list of objects + /// Id of the to retrieve Relations for + /// An enumerable list of objects IEnumerable GetByRelationTypeId(int relationTypeId); + /// + /// Gets a paged result of + /// + /// + /// + /// + /// + /// + IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null); + /// /// Gets the Child object from a Relation as an /// @@ -222,7 +232,7 @@ namespace Umbraco.Core.Services /// Id of the parent /// Id of the child /// The type of relation to create - /// The created + /// The created IRelation Relate(int parentId, int childId, IRelationType relationType); /// @@ -231,7 +241,7 @@ namespace Umbraco.Core.Services /// Parent entity /// Child entity /// The type of relation to create - /// The created + /// The created IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType); /// @@ -240,7 +250,7 @@ namespace Umbraco.Core.Services /// Id of the parent /// Id of the child /// Alias of the type of relation to create - /// The created + /// The created IRelation Relate(int parentId, int childId, string relationTypeAlias); /// @@ -249,14 +259,14 @@ namespace Umbraco.Core.Services /// Parent entity /// Child entity /// Alias of the type of relation to create - /// The created + /// The created IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias); /// - /// Checks whether any relations exists for the passed in . + /// Checks whether any relations exists for the passed in . /// - /// to check for relations - /// Returns True if any relations exists for the given , otherwise False + /// to check for relations + /// Returns True if any relations exists for the given , otherwise False bool HasRelations(IRelationType relationType); /// @@ -301,7 +311,7 @@ namespace Umbraco.Core.Services bool AreRelated(int parentId, int childId, string relationTypeAlias); /// - /// Saves a + /// Saves a /// /// Relation to save void Save(IRelation relation); @@ -309,27 +319,27 @@ namespace Umbraco.Core.Services void Save(IEnumerable relations); /// - /// Saves a + /// Saves a /// /// RelationType to Save void Save(IRelationType relationType); /// - /// Deletes a + /// Deletes a /// /// Relation to Delete void Delete(IRelation relation); /// - /// Deletes a + /// Deletes a /// /// RelationType to Delete void Delete(IRelationType relationType); /// - /// Deletes all objects based on the passed in + /// Deletes all objects based on the passed in /// - /// to Delete Relations for + /// to Delete Relations for void DeleteRelationsOfType(IRelationType relationType); } } diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs index 860286e1bb..11572f09ff 100644 --- a/src/Umbraco.Core/Services/Implement/RelationService.cs +++ b/src/Umbraco.Core/Services/Implement/RelationService.cs @@ -207,6 +207,16 @@ namespace Umbraco.Core.Services.Implement } } + /// + public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + var query = Query().Where(x => x.RelationTypeId == relationTypeId); + return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering); + } + } + /// public IUmbracoEntity GetChildEntityFromRelation(IRelation relation) { diff --git a/src/Umbraco.Tests/Services/RelationServiceTests.cs b/src/Umbraco.Tests/Services/RelationServiceTests.cs index 42b6f45047..2ec10811b7 100644 --- a/src/Umbraco.Tests/Services/RelationServiceTests.cs +++ b/src/Umbraco.Tests/Services/RelationServiceTests.cs @@ -16,6 +16,54 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class RelationServiceTests : TestWithSomeContentBase { + + [Test] + public void Get_Paged_Relations_By_Relation_Type() + { + //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); + } + + var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias); + + // Relate content to media + foreach (var content in createdContent) + foreach (var media in createdMedia) + ServiceContext.RelationService.Relate(content.Id, media.Id, relType); + + var paged = ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 0, 51, out var totalRecs).ToList(); + + Assert.AreEqual(100, totalRecs); + Assert.AreEqual(51, paged.Count); + + //next page + paged.AddRange(ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 1, 51, out totalRecs)); + + Assert.AreEqual(100, totalRecs); + Assert.AreEqual(100, paged.Count); + + Assert.IsTrue(createdContent.Select(x => x.Id).ContainsAll(paged.Select(x => x.ParentId))); + Assert.IsTrue(createdMedia.Select(x => x.Id).ContainsAll(paged.Select(x => x.ChildId))); + } + [Test] public void Return_List_Of_Content_Items_Where_Media_Item_Referenced() {