From 0619a7de417bf32d7568a7fe3b0ce1a1bc66cd8f Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 21 Feb 2023 15:20:34 +0100 Subject: [PATCH] Use keys for tracked references (#13849) * Use Keys for tracked references instead of int id's. * Updated OpenApi.json * Handle filterMustBeIsDependency consistently --------- Co-authored-by: Nikolaj --- .../ByIdTrackedReferenceController.cs | 12 +- .../DescendantsTrackedReferenceController.cs | 6 +- .../ItemsTrackedReferenceController.cs | 6 +- ...TrackedReferenceViewModelsMapDefinition.cs | 1 + src/Umbraco.Cms.Api.Management/OpenApi.json | 427 +++++++++--------- .../RelationItemViewModel.cs | 3 + src/Umbraco.Core/Models/RelationItemModel.cs | 2 + .../ITrackedReferencesRepository.cs | 36 +- .../Services/ITrackedReferencesService.cs | 27 +- .../Services/TrackedReferencesService.cs | 61 ++- .../Implement/TrackedReferencesRepository.cs | 216 ++++++++- .../Persistence/UmbracoDatabaseExtensions.cs | 7 + .../Mappers/RelationModelMapDefinition.cs | 2 + 13 files changed, 552 insertions(+), 254 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs index 39d2cfe3c2..57b24dd4b4 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs @@ -27,16 +27,16 @@ public class ByIdTrackedReferenceController : TrackedReferenceControllerBase /// Used by info tabs on content, media etc. and for the delete and unpublish of single items. /// This is basically finding parents of relations. /// - [HttpGet("{id:int}")] + [HttpGet("{key:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] public async Task>> Get( - int id, - long skip, - long take, - bool? filterMustBeIsDependency) + Guid key, + long skip = 0, + long take = 20, + bool filterMustBeIsDependency = false) { - PagedModel relationItems = _trackedReferencesService.GetPagedRelationsForItem(id, skip, take, filterMustBeIsDependency ?? false); + PagedModel relationItems = await _trackedReferencesService.GetPagedRelationsForItemAsync(key, skip, take, filterMustBeIsDependency); var pagedViewModel = new PagedViewModel { diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs index 752b6d8846..899b8ff500 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs @@ -28,12 +28,12 @@ public class DescendantsTrackedReferenceController : TrackedReferenceControllerB /// kind of relation. /// This is basically finding the descending items which are children in relations. /// - [HttpGet("descendants/{parentId:int}")] + [HttpGet("descendants/{parentKey:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Descendants(int parentId, long skip, long take, bool? filterMustBeIsDependency) + public async Task>> Descendants(Guid parentKey, long skip, long take, bool filterMustBeIsDependency = true) { - PagedModel relationItems = _trackedReferencesSkipTakeService.GetPagedDescendantsInReferences(parentId, skip, take, filterMustBeIsDependency ?? true); + PagedModel relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(parentKey, skip, take, filterMustBeIsDependency); var pagedViewModel = new PagedViewModel { Total = relationItems.Total, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs index ecc0e3434d..59e3ba0503 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs @@ -21,7 +21,7 @@ public class ItemsTrackedReferenceController : TrackedReferenceControllerBase } /// - /// Gets a page list of the items used in any kind of relation from selected integer ids. + /// Gets a page list of the items used in any kind of relation from selected keys. /// /// /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view). @@ -30,9 +30,9 @@ public class ItemsTrackedReferenceController : TrackedReferenceControllerBase [HttpGet("item")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> GetPagedReferencedItems([FromQuery]int[] ids, long skip, long take, bool? filterMustBeIsDependency) + public async Task>> GetPagedReferencedItems([FromQuery(Name="key")]SortedSet keys, long skip = 0, long take = 20, bool filterMustBeIsDependency = true) { - PagedModel relationItems = _trackedReferencesSkipTakeService.GetPagedItemsWithRelations(ids, skip, take, filterMustBeIsDependency ?? true); + PagedModel relationItems = await _trackedReferencesSkipTakeService.GetPagedItemsWithRelationsAsync(keys, skip, take, filterMustBeIsDependency); var pagedViewModel = new PagedViewModel { Total = relationItems.Total, diff --git a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs index 9b36669fcf..f7b75cc692 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs @@ -23,6 +23,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition target.RelationTypeIsBidirectional = source.RelationTypeIsBidirectional; target.RelationTypeIsDependency = source.RelationTypeIsDependency; target.RelationTypeName = source.RelationTypeName; + target.NodePublished = source.NodePublished; } } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index fb12d7a5c4..e686661935 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -702,6 +702,9 @@ } } }, + "404": { + "description": "Not Found" + }, "400": { "description": "Bad Request", "content": { @@ -712,9 +715,6 @@ } } }, - "404": { - "description": "Not Found" - }, "409": { "description": "Conflict", "content": { @@ -998,9 +998,7 @@ ], "operationId": "PostDictionaryUpload", "requestBody": { - "content": { - - } + "content": { } }, "responses": { "200": { @@ -1496,6 +1494,9 @@ } ], "responses": { + "401": { + "description": "Unauthorized" + }, "200": { "description": "Success", "content": { @@ -1505,9 +1506,6 @@ } } } - }, - "401": { - "description": "Unauthorized" } } } @@ -1539,6 +1537,9 @@ } ], "responses": { + "401": { + "description": "Unauthorized" + }, "200": { "description": "Success", "content": { @@ -1548,9 +1549,6 @@ } } } - }, - "401": { - "description": "Unauthorized" } } } @@ -1785,6 +1783,9 @@ } ], "responses": { + "404": { + "description": "Not Found" + }, "200": { "description": "Success", "content": { @@ -1798,9 +1799,6 @@ } } } - }, - "404": { - "description": "Not Found" } } } @@ -1822,6 +1820,9 @@ } ], "responses": { + "404": { + "description": "Not Found" + }, "200": { "description": "Success", "content": { @@ -1835,9 +1836,6 @@ } } } - }, - "404": { - "description": "Not Found" } } } @@ -1862,6 +1860,16 @@ } }, "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "200": { "description": "Success", "content": { @@ -1875,16 +1883,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } } } } @@ -1936,16 +1934,6 @@ } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PagedHelpPageModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -1955,6 +1943,16 @@ } } } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedHelpPageModel" + } + } + } } } } @@ -2014,6 +2012,16 @@ } ], "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "200": { "description": "Success", "content": { @@ -2027,16 +2035,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } } } } @@ -2058,16 +2056,6 @@ } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OkResultModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -2077,6 +2065,16 @@ } } } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OkResultModel" + } + } + } } } } @@ -2088,20 +2086,6 @@ ], "operationId": "GetInstallSettings", "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InstallSettingsModel" - } - ] - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -2121,6 +2105,20 @@ } } } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InstallSettingsModel" + } + ] + } + } + } } } } @@ -2145,9 +2143,6 @@ } }, "responses": { - "200": { - "description": "Success" - }, "400": { "description": "Bad Request", "content": { @@ -2167,6 +2162,9 @@ } } } + }, + "200": { + "description": "Success" } } } @@ -2191,9 +2189,6 @@ } }, "responses": { - "200": { - "description": "Success" - }, "400": { "description": "Bad Request", "content": { @@ -2203,6 +2198,9 @@ } } } + }, + "200": { + "description": "Success" } } } @@ -2265,6 +2263,19 @@ } }, "responses": { + "404": { + "description": "Not Found" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "201": { "description": "Created", "headers": { @@ -2277,19 +2288,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } - }, - "404": { - "description": "Not Found" } } } @@ -2311,6 +2309,9 @@ } ], "responses": { + "404": { + "description": "Not Found" + }, "200": { "description": "Success", "content": { @@ -2324,9 +2325,6 @@ } } } - }, - "404": { - "description": "Not Found" } } }, @@ -2346,9 +2344,6 @@ } ], "responses": { - "200": { - "description": "Success" - }, "400": { "description": "Bad Request", "content": { @@ -2368,6 +2363,9 @@ } } } + }, + "200": { + "description": "Success" } } }, @@ -2400,8 +2398,8 @@ } }, "responses": { - "200": { - "description": "Success" + "404": { + "description": "Not Found" }, "400": { "description": "Bad Request", @@ -2413,8 +2411,8 @@ } } }, - "404": { - "description": "Not Found" + "200": { + "description": "Success" } } } @@ -2484,9 +2482,6 @@ } ], "responses": { - "200": { - "description": "Success" - }, "400": { "description": "Bad Request", "content": { @@ -2496,6 +2491,9 @@ } } } + }, + "200": { + "description": "Success" } } } @@ -2623,16 +2621,6 @@ } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PagedLogTemplateModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -2642,6 +2630,16 @@ } } } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedLogTemplateModel" + } + } + } } } } @@ -2704,6 +2702,16 @@ } }, "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "201": { "description": "Created", "headers": { @@ -2716,16 +2724,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } } } } @@ -2747,6 +2745,9 @@ } ], "responses": { + "404": { + "description": "Not Found" + }, "200": { "description": "Success", "content": { @@ -2760,9 +2761,6 @@ } } } - }, - "404": { - "description": "Not Found" } } }, @@ -2782,11 +2780,11 @@ } ], "responses": { - "200": { - "description": "Success" - }, "404": { "description": "Not Found" + }, + "200": { + "description": "Success" } } } @@ -2816,9 +2814,6 @@ } ], "responses": { - "200": { - "description": "Success" - }, "400": { "description": "Bad Request", "content": { @@ -2828,6 +2823,9 @@ } } } + }, + "200": { + "description": "Success" } } } @@ -3014,6 +3012,9 @@ } ], "responses": { + "401": { + "description": "Unauthorized" + }, "200": { "description": "Success", "content": { @@ -3023,9 +3024,6 @@ } } } - }, - "401": { - "description": "Unauthorized" } } } @@ -3057,6 +3055,9 @@ } ], "responses": { + "401": { + "description": "Unauthorized" + }, "200": { "description": "Success", "content": { @@ -3066,9 +3067,6 @@ } } } - }, - "401": { - "description": "Unauthorized" } } } @@ -3730,16 +3728,6 @@ } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PagedRedirectUrlModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -3749,6 +3737,16 @@ } } } + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedRedirectUrlModel" + } + } + } } } } @@ -4300,6 +4298,16 @@ ], "operationId": "GetServerStatus", "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "200": { "description": "Success", "content": { @@ -4313,16 +4321,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } } } } @@ -4334,6 +4332,16 @@ ], "operationId": "GetServerVersion", "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "200": { "description": "Success", "content": { @@ -4347,16 +4355,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } } } } @@ -4693,9 +4691,6 @@ } }, "responses": { - "200": { - "description": "Success" - }, "400": { "description": "Bad Request", "content": { @@ -4705,6 +4700,9 @@ } } } + }, + "200": { + "description": "Success" } } } @@ -5110,20 +5108,20 @@ } } }, - "/umbraco/management/api/v1/tracked-reference/{id}": { + "/umbraco/management/api/v1/tracked-reference/{key}": { "get": { "tags": [ "Tracked Reference" ], - "operationId": "GetTrackedReferenceById", + "operationId": "GetTrackedReferenceByKey", "parameters": [ { - "name": "id", + "name": "key", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "uuid" } }, { @@ -5131,7 +5129,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 0 } }, { @@ -5139,14 +5138,16 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 20 } }, { "name": "filterMustBeIsDependency", "in": "query", "schema": { - "type": "boolean" + "type": "boolean", + "default": false } } ], @@ -5164,20 +5165,20 @@ } } }, - "/umbraco/management/api/v1/tracked-reference/descendants/{parentId}": { + "/umbraco/management/api/v1/tracked-reference/descendants/{parentKey}": { "get": { "tags": [ "Tracked Reference" ], - "operationId": "GetTrackedReferenceDescendantsByParentId", + "operationId": "GetTrackedReferenceDescendantsByParentKey", "parameters": [ { - "name": "parentId", + "name": "parentKey", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "uuid" } }, { @@ -5226,13 +5227,14 @@ "operationId": "GetTrackedReferenceItem", "parameters": [ { - "name": "ids", + "name": "key", "in": "query", "schema": { + "uniqueItems": true, "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "uuid" } } }, @@ -5241,7 +5243,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 0 } }, { @@ -5249,14 +5252,16 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 20 } }, { "name": "filterMustBeIsDependency", "in": "query", "schema": { - "type": "boolean" + "type": "boolean", + "default": true } } ], @@ -5361,6 +5366,16 @@ } }, "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, "201": { "description": "Created", "headers": { @@ -5373,16 +5388,6 @@ } } } - }, - "400": { - "description": "Bad Request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetailsModel" - } - } - } } } }, @@ -6765,9 +6770,7 @@ }, "providerProperties": { "type": "object", - "additionalProperties": { - - }, + "additionalProperties": { }, "nullable": true } }, @@ -7696,9 +7699,7 @@ "nullable": true } }, - "additionalProperties": { - - } + "additionalProperties": { } }, "ProfilingStatusModel": { "type": "object", @@ -7919,6 +7920,10 @@ "type": "string", "nullable": true }, + "nodePublished": { + "type": "boolean", + "nullable": true + }, "contentTypeIcon": { "type": "string", "nullable": true @@ -8561,9 +8566,7 @@ "authorizationCode": { "authorizationUrl": "/umbraco/management/api/v1.0/security/back-office/authorize", "tokenUrl": "/umbraco/management/api/v1.0/security/back-office/token", - "scopes": { - - } + "scopes": { } } } } @@ -8571,9 +8574,7 @@ }, "security": [ { - "OAuth": [ - - ] + "OAuth": [ ] } ] } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs index f27c81f3eb..572da9d9ea 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs @@ -8,6 +8,8 @@ public class RelationItemViewModel public string? NodeType { get; set; } + public bool? NodePublished { get; set; } + public string? ContentTypeIcon { get; set; } public string? ContentTypeAlias { get; set; } @@ -19,4 +21,5 @@ public class RelationItemViewModel public bool RelationTypeIsBidirectional { get; set; } public bool RelationTypeIsDependency { get; set; } + } diff --git a/src/Umbraco.Core/Models/RelationItemModel.cs b/src/Umbraco.Core/Models/RelationItemModel.cs index ee45422586..a05c8f6591 100644 --- a/src/Umbraco.Core/Models/RelationItemModel.cs +++ b/src/Umbraco.Core/Models/RelationItemModel.cs @@ -8,6 +8,8 @@ public class RelationItemModel public string? NodeType { get; set; } + public bool? NodePublished { get; set; } + public string? ContentTypeIcon { get; set; } public string? ContentTypeAlias { get; set; } diff --git a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs index bd6723e674..9466a1fcd8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ITrackedReferencesRepository.cs @@ -17,6 +17,7 @@ public interface ITrackedReferencesRepository /// /// The total count of the items with reference to the current item. /// An enumerable list of objects. + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); /// @@ -31,6 +32,7 @@ public interface ITrackedReferencesRepository /// /// The total count of the items in any kind of relation. /// An enumerable list of objects. + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); /// @@ -45,6 +47,7 @@ public interface ITrackedReferencesRepository /// /// The total count of descending items. /// An enumerable list of objects. + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords); /// @@ -60,6 +63,15 @@ public interface ITrackedReferencesRepository /// /// The total count of the items with reference to the current item. /// An enumerable list of objects. + IEnumerable GetPagedRelationsForItem( + Guid key, + long skip, + long take, + bool filterMustBeIsDependency, + out long totalRecords) => + throw new NotImplementedException(); + + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] IEnumerable GetPagedRelationsForItem( int id, long skip, @@ -80,18 +92,25 @@ public interface ITrackedReferencesRepository /// /// The total count of the items in any kind of relation. /// An enumerable list of objects. + IEnumerable GetPagedItemsWithRelations( + ISet keys, + long skip, + long take, + bool filterMustBeIsDependency, + out long totalRecords); + + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] IEnumerable GetPagedItemsWithRelations( int[] ids, long skip, long take, bool filterMustBeIsDependency, - out long totalRecords) => - throw new NotImplementedException(); + out long totalRecords); /// /// Gets a page of the descending items that have any references, given a parent id. /// - /// The unique identifier of the parent to retrieve descendants for. + /// The unique identifier of the parent to retrieve descendants for. /// The amount of items to skip. /// The amount of items to take. /// @@ -100,11 +119,18 @@ public interface ITrackedReferencesRepository /// /// The total count of descending items. /// An enumerable list of objects. + IEnumerable GetPagedDescendantsInReferences( + Guid parentKey, + long skip, + long take, + bool filterMustBeIsDependency, + out long totalRecords); + + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] IEnumerable GetPagedDescendantsInReferences( int parentId, long skip, long take, bool filterMustBeIsDependency, - out long totalRecords) => - throw new NotImplementedException(); + out long totalRecords); } diff --git a/src/Umbraco.Core/Services/ITrackedReferencesService.cs b/src/Umbraco.Core/Services/ITrackedReferencesService.cs index 94a8871e7f..50730c4f07 100644 --- a/src/Umbraco.Core/Services/ITrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/ITrackedReferencesService.cs @@ -17,6 +17,7 @@ public interface ITrackedReferencesService /// dependencies (isDependency field is set to true). /// /// A paged result of objects. + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency); /// @@ -30,6 +31,7 @@ public interface ITrackedReferencesService /// dependencies (isDependency field is set to true). /// /// A paged result of objects. + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency); /// @@ -43,9 +45,13 @@ public interface ITrackedReferencesService /// dependencies (isDependency field is set to true). /// /// A paged result of objects. + [Obsolete("Use method that takes key (Guid) instead of id (int). This will be removed in Umbraco 15.")] PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency); - /// + [Obsolete("Use method that takes key (Guid) instead of id (int). This will be removed in Umbraco 15.")] + PagedModel GetPagedRelationsForItem(int id, long skip, long take, bool filterMustBeIsDependency); + + /// /// Gets a paged result of items which are in relation with the current item. /// Basically, shows the items which depend on the current item. /// @@ -58,26 +64,32 @@ public interface ITrackedReferencesService /// /// The total amount of items. /// A paged result of objects. - PagedModel GetPagedRelationsForItem(int id, long skip, long take, bool filterMustBeIsDependency) => throw new NotImplementedException(); + Task> GetPagedRelationsForItemAsync(Guid key, long skip, long take, bool filterMustBeIsDependency); + + [Obsolete("Use method that takes key (Guid) instead of id (int). This will be removed in Umbraco 15.")] + PagedModel GetPagedDescendantsInReferences(int parentId, long skip, long take, bool filterMustBeIsDependency); /// /// Gets a paged result of the descending items that have any references, given a parent id. /// - /// The unique identifier of the parent to retrieve descendants for. + /// The unique identifier of the parent to retrieve descendants for. /// The amount of items to skip /// The amount of items to take. /// /// A boolean indicating whether to filter only the RelationTypes which are /// dependencies (isDependency field is set to true). /// - /// The total amount of items. /// A paged result of objects. - PagedModel GetPagedDescendantsInReferences(int parentId, long skip, long take, bool filterMustBeIsDependency) => throw new NotImplementedException(); + Task> GetPagedDescendantsInReferencesAsync(Guid parentKey, long skip, long take, bool filterMustBeIsDependency); + + [Obsolete("Use method that takes keys (Guid) instead of ids (int). This will be removed in Umbraco 15.")] + PagedModel GetPagedItemsWithRelations(int[] ids, long skip, long take, + bool filterMustBeIsDependency); /// /// Gets a paged result of items used in any kind of relation from selected integer ids. /// - /// The identifiers of the entities to check for relations. + /// The identifiers of the entities to check for relations. /// The amount of items to skip /// The amount of items to take. /// @@ -86,5 +98,6 @@ public interface ITrackedReferencesService /// /// The total amount of items. /// A paged result of objects. - PagedModel GetPagedItemsWithRelations(int[] ids, long skip, long take, bool filterMustBeIsDependency) => throw new NotImplementedException(); + Task> GetPagedItemsWithRelationsAsync(ISet keys, long skip, long take, + bool filterMustBeIsDependency); } diff --git a/src/Umbraco.Core/Services/TrackedReferencesService.cs b/src/Umbraco.Core/Services/TrackedReferencesService.cs index 280e648327..3b2a89a531 100644 --- a/src/Umbraco.Core/Services/TrackedReferencesService.cs +++ b/src/Umbraco.Core/Services/TrackedReferencesService.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; @@ -8,28 +10,33 @@ namespace Umbraco.Cms.Core.Services; public class TrackedReferencesService : ITrackedReferencesService { private readonly ICoreScopeProvider _scopeProvider; + private readonly IEntityService _entityService; private readonly ITrackedReferencesRepository _trackedReferencesRepository; - [Obsolete("Please use ctor that does not take an IEntityService, scheduled for removal in V12")] + public TrackedReferencesService( ITrackedReferencesRepository trackedReferencesRepository, ICoreScopeProvider scopeProvider, - IEntityService entityService) : this(trackedReferencesRepository, scopeProvider) - { - } - - public TrackedReferencesService( - ITrackedReferencesRepository trackedReferencesRepository, - ICoreScopeProvider scopeProvider) + IEntityService entityService) { _trackedReferencesRepository = trackedReferencesRepository; _scopeProvider = scopeProvider; + _entityService = entityService; + } + + [Obsolete("Please use ctor that does not take an IEntityService, scheduled for removal in V15")] + public TrackedReferencesService( + ITrackedReferencesRepository trackedReferencesRepository, + ICoreScopeProvider scopeProvider): this(trackedReferencesRepository, scopeProvider, StaticServiceProvider.Instance.GetRequiredService()) + { + } /// /// Gets a paged result of items which are in relation with the current item. /// Basically, shows the items which depend on the current item. /// + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public PagedResult GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -41,6 +48,7 @@ public class TrackedReferencesService : ITrackedReferencesService /// /// Gets a paged result of items used in any kind of relation from selected integer ids. /// + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public PagedResult GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -52,6 +60,7 @@ public class TrackedReferencesService : ITrackedReferencesService /// /// Gets a paged result of the descending items that have any references, given a parent id. /// + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public PagedResult GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -65,6 +74,7 @@ public class TrackedReferencesService : ITrackedReferencesService return new PagedResult(totalItems, pageIndex + 1, pageSize) { Items = items }; } + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public PagedModel GetPagedRelationsForItem(int id, long skip, long take, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -74,6 +84,16 @@ public class TrackedReferencesService : ITrackedReferencesService return pagedModel; } + public async Task> GetPagedRelationsForItemAsync(Guid key, long skip, long take, bool filterMustBeIsDependency) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + IEnumerable items = _trackedReferencesRepository.GetPagedRelationsForItem(key, skip, take, filterMustBeIsDependency, out var totalItems); + var pagedModel = new PagedModel(totalItems, items); + + return await Task.FromResult(pagedModel); + } + + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public PagedModel GetPagedDescendantsInReferences(int parentId, long skip, long take, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -89,6 +109,22 @@ public class TrackedReferencesService : ITrackedReferencesService return pagedModel; } + public async Task> GetPagedDescendantsInReferencesAsync(Guid parentKey, long skip, long take, bool filterMustBeIsDependency) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + + IEnumerable items = _trackedReferencesRepository.GetPagedDescendantsInReferences( + parentKey, + skip, + take, + filterMustBeIsDependency, + out var totalItems); + var pagedModel = new PagedModel(totalItems, items); + + return await Task.FromResult(pagedModel); + } + + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public PagedModel GetPagedItemsWithRelations(int[] ids, long skip, long take, bool filterMustBeIsDependency) { using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); @@ -97,4 +133,13 @@ public class TrackedReferencesService : ITrackedReferencesService return pagedModel; } + + public async Task> GetPagedItemsWithRelationsAsync(ISet keys, long skip, long take, bool filterMustBeIsDependency) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); + IEnumerable items = _trackedReferencesRepository.GetPagedItemsWithRelations(keys, skip, take, filterMustBeIsDependency, out var totalItems); + var pagedModel = new PagedModel(totalItems, items); + + return await Task.FromResult(pagedModel); + } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs index c7966daec1..0a6ec43e0d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TrackedReferencesRepository.cs @@ -92,27 +92,39 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement } var innerUnionSqlChild = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( - "[cr].childId as id", "[cr].parentId as otherId", "[rt].[alias]", "[rt].[name]", + "[cn].uniqueId as key", "[pn].uniqueId as otherKey, [cr].childId as id", "[cr].parentId as otherId", "[rt].[alias]", "[rt].[name]", "[rt].[isDependency]", "[rt].[dual]") .From("cr") .InnerJoin("rt") - .On((cr, rt) => rt.Dual == false && rt.Id == cr.RelationType, "cr", "rt"); + .On((cr, rt) => rt.Dual == false && rt.Id == cr.RelationType, "cr", "rt") + .InnerJoin("cn") + .On((cr, cn) => cr.ChildId == cn.NodeId, "cr", "cn") + .InnerJoin("pn") + .On((cr, pn) => cr.ParentId == pn.NodeId, "cr", "pn"); var innerUnionSqlDualParent = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( - "[dpr].parentId as id", "[dpr].childId as otherId", "[dprt].[alias]", "[dprt].[name]", + "[pn].uniqueId as key", "[cn].uniqueId as otherKey, [dpr].parentId as id", "[dpr].childId as otherId", "[dprt].[alias]", "[dprt].[name]", "[dprt].[isDependency]", "[dprt].[dual]") .From("dpr") .InnerJoin("dprt") .On( - (dpr, dprt) => dprt.Dual == true && dprt.Id == dpr.RelationType, "dpr", "dprt"); + (dpr, dprt) => dprt.Dual == true && dprt.Id == dpr.RelationType, "dpr", "dprt") + .InnerJoin("cn") + .On((dpr, cn) => dpr.ChildId == cn.NodeId, "dpr", "cn") + .InnerJoin("pn") + .On((dpr, pn) => dpr.ParentId == pn.NodeId, "dpr", "pn"); var innerUnionSql3 = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().Select( - "[dcr].childId as id", "[dcr].parentId as otherId", "[dcrt].[alias]", "[dcrt].[name]", + "[cn].uniqueId as key", "[pn].uniqueId as otherKey, [dcr].childId as id", "[dcr].parentId as otherId", "[dcrt].[alias]", "[dcrt].[name]", "[dcrt].[isDependency]", "[dcrt].[dual]") .From("dcr") .InnerJoin("dcrt") .On( - (dcr, dcrt) => dcrt.Dual == true && dcrt.Id == dcr.RelationType, "dcr", "dcrt"); + (dcr, dcrt) => dcrt.Dual == true && dcrt.Id == dcr.RelationType, "dcr", "dcrt") + .InnerJoin("cn") + .On((dcr, cn) => dcr.ChildId == cn.NodeId, "dcr", "cn") + .InnerJoin("pn") + .On((dcr, pn) => dcr.ParentId == pn.NodeId, "dcr", "pn"); var innerUnionSql = innerUnionSqlChild.Union(innerUnionSqlDualParent).Union(innerUnionSql3); @@ -255,6 +267,69 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement return pagedResult?.Items.Select(MapDtoToEntity) ?? Enumerable.Empty(); } + public IEnumerable GetPagedRelationsForItem( + Guid key, + long skip, + long take, + bool filterMustBeIsDependency, + out long totalRecords) + { + Sql innerUnionSql = GetInnerUnionSql(); + var sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct( + "[x].[otherId] as nodeId", + "[n].[uniqueId] as nodeKey", + "[n].[text] as nodeName", + "[n].[nodeObjectType] as nodeObjectType", + "[d].[published] as nodePublished", + "[ct].[icon] as contentTypeIcon", + "[ct].[alias] as contentTypeAlias", + "[ctn].[text] as contentTypeName", + "[x].[alias] as relationTypeAlias", + "[x].[name] as relationTypeName", + "[x].[isDependency] as relationTypeIsDependency", + "[x].[dual] as relationTypeIsBidirectional") + .From("n") + .InnerJoinNested(innerUnionSql, "x") + .On((n, x) => n.NodeId == x.OtherId, "n", "x") + .LeftJoin("c") + .On( + (left, right) => left.NodeId == right.NodeId, + aliasLeft: "n", + aliasRight: "c") + .LeftJoin("ct") + .On( + (left, right) => left.ContentTypeId == right.NodeId, + aliasLeft: "c", + aliasRight: "ct") + .LeftJoin("ctn") + .On( + (left, right) => left.NodeId == right.NodeId, + aliasLeft: "ct", + aliasRight: "ctn") + .LeftJoin("d") + .On( + (left, right) => left.NodeId == right.NodeId, + aliasLeft: "n", + aliasRight: "d") + .Where(x => x.Key == key, "x"); + + if (filterMustBeIsDependency) + { + sql = sql?.Where(rt => rt.IsDependency, "x"); + } + + // Ordering is required for paging + sql = sql?.OrderBy(x => x.Alias, "x"); + + RelationItemDto[] pagedResult = + _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? + Array.Empty(); + totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0; + + return _umbracoMapper.MapEnumerable(pagedResult); + } + + [Obsolete("Use overload that takes key instead of id. This will be removed in Umbraco 15.")] public IEnumerable GetPagedRelationsForItem( int id, long skip, @@ -299,11 +374,130 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement RelationItemDto[] pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? Array.Empty(); - totalRecords = pagedResult.Length; + totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0; return _umbracoMapper.MapEnumerable(pagedResult); } + public IEnumerable GetPagedItemsWithRelations( + ISet keys, + long skip, + long take, + bool filterMustBeIsDependency, + out long totalRecords) + { + Sql innerUnionSql = GetInnerUnionSql(); + Sql? sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct( + "[x].[id] as nodeId", + "[n].[uniqueId] as nodeKey", + "[n].[text] as nodeName", + "[n].[nodeObjectType] as nodeObjectType", + "[ct].[icon] as contentTypeIcon", + "[ct].[alias] as contentTypeAlias", + "[ctn].[text] as contentTypeName", + "[x].[alias] as relationTypeAlias", + "[x].[name] as relationTypeName", + "[x].[isDependency] as relationTypeIsDependency", + "[x].[dual] as relationTypeIsBidirectional") + .From("n") + .InnerJoinNested(innerUnionSql, "x") + .On((n, x) => n.NodeId == x.Id, "n", "x") + .LeftJoin("c") + .On((left, right) => left.NodeId == right.NodeId, aliasLeft: "n", aliasRight: "c") + .LeftJoin("ct") + .On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", + aliasRight: "ct") + .LeftJoin("ctn") + .On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", + aliasRight: "ctn"); + if (keys.Any()) + { + sql = sql?.Where(x => keys.Contains(x.UniqueId), "n"); + } + + if (filterMustBeIsDependency) + { + sql = sql?.Where(rt => rt.IsDependency, "x"); + } + + // Ordering is required for paging + sql = sql?.OrderBy(x => x.Alias, "x"); + + RelationItemDto[] pagedResult = + _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? + Array.Empty(); + + totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0; + + return _umbracoMapper.MapEnumerable(pagedResult); + } + + public IEnumerable GetPagedDescendantsInReferences(Guid parentKey, long skip, long take, bool filterMustBeIsDependency, + out long totalRecords) + { + var syntax = _scopeAccessor.AmbientScope?.Database.SqlContext.SqlSyntax; + + // Gets the path of the parent with ",%" added + var subsubQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + .Select(syntax?.GetConcat("[node].[path]", "',%'")) + .From("node") + .Where(x => x.UniqueId == parentKey, "node"); + + // Gets the descendants of the parent node + Sql? subQuery = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql() + .Select(x => x.NodeId) + .From() + .WhereLike(x => x.Path, subsubQuery); + + Sql innerUnionSql = GetInnerUnionSql(); + var sql = _scopeAccessor.AmbientScope?.Database.SqlContext.Sql().SelectDistinct( + "[x].[id] as nodeId", + "[n].[uniqueId] as nodeKey", + "[n].[text] as nodeName", + "[n].[nodeObjectType] as nodeObjectType", + "[d].[published] as nodePublished", + "[ct].[icon] as contentTypeIcon", + "[ct].[alias] as contentTypeAlias", + "[ctn].[text] as contentTypeName", + "[x].[alias] as relationTypeAlias", + "[x].[name] as relationTypeName", + "[x].[isDependency] as relationTypeIsDependency", + "[x].[dual] as relationTypeIsBidirectional") + .From("n") + .InnerJoinNested(innerUnionSql, "x") + .On((n, x) => n.NodeId == x.Id, "n", "x") + .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, + aliasLeft: "n", aliasRight: "c") + .LeftJoin("ct") + .On((left, right) => left.ContentTypeId == right.NodeId, aliasLeft: "c", + aliasRight: "ct") + .LeftJoin("ctn") + .On((left, right) => left.NodeId == right.NodeId, aliasLeft: "ct", + aliasRight: "ctn") + .LeftJoin("d") + .On( + (left, right) => left.NodeId == right.NodeId, + aliasLeft: "n", + aliasRight: "d"); + sql = sql?.WhereIn((System.Linq.Expressions.Expression>)(x => x.NodeId), subQuery, + "n"); + + if (filterMustBeIsDependency) + { + sql = sql?.Where(rt => rt.IsDependency, "x"); + } + + // Ordering is required for paging + sql = sql?.OrderBy(x => x.Alias, "x"); + + List? pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql); + totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0; + + return _umbracoMapper.MapEnumerable(pagedResult ?? + new List()); + } + + [Obsolete("Use overload that takes keys instead of ids. This will be removed in Umbraco 15.")] public IEnumerable GetPagedItemsWithRelations( int[] ids, long skip, @@ -351,7 +545,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement RelationItemDto[] pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql).ToArray() ?? Array.Empty(); - totalRecords = pagedResult.Length; + totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0; return _umbracoMapper.MapEnumerable(pagedResult); } @@ -414,7 +608,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement List? pagedResult = _scopeAccessor.AmbientScope?.Database.SkipTake(skip, take, sql); - totalRecords = pagedResult?.Count ?? 0; + totalRecords = _scopeAccessor.AmbientScope?.Database.Count(sql!) ?? 0; return _umbracoMapper.MapEnumerable(pagedResult ?? new List()); @@ -426,6 +620,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement [Column("otherId")] public int OtherId { get; set; } + [Column("key")] public Guid Key { get; set; } + + [Column("otherKey")] public Guid OtherKey { get; set; } + [Column("alias")] public string? Alias { get; set; } [Column("name")] public string? Name { get; set; } diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs index 78bcc34f2b..29de5c0506 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseExtensions.cs @@ -73,4 +73,11 @@ internal static class UmbracoDatabaseExtensions /// public static bool IsDatabaseEmpty(this IUmbracoDatabase database) => database.SqlContext.SqlSyntax.GetTablesInSchema(database).Any() == false; + + public static long Count(this IUmbracoDatabase database, Sql sql) + { + var query = new Sql().Select("COUNT(*)").From().Append("(").Append(sql).Append(")"); + + return database.ExecuteScalar(query); + } } diff --git a/src/Umbraco.New.Cms.Infrastructure/Persistence/Mappers/RelationModelMapDefinition.cs b/src/Umbraco.New.Cms.Infrastructure/Persistence/Mappers/RelationModelMapDefinition.cs index 44dfc3bd4a..fb2833f9b6 100644 --- a/src/Umbraco.New.Cms.Infrastructure/Persistence/Mappers/RelationModelMapDefinition.cs +++ b/src/Umbraco.New.Cms.Infrastructure/Persistence/Mappers/RelationModelMapDefinition.cs @@ -22,5 +22,7 @@ public class RelationModelMapDefinition : IMapDefinition target.ContentTypeAlias = source.ChildContentTypeAlias; target.ContentTypeIcon = source.ChildContentTypeIcon; target.ContentTypeName = source.ChildContentTypeName; + + target.NodePublished = source.ChildNodePublished; } }