using System; using System.Collections.Generic; using System.Linq; using NPoco; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Extensions; namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { internal class TrackedReferencesRepository : ITrackedReferencesRepository { private readonly IScopeAccessor _scopeAccessor; public TrackedReferencesRepository(IScopeAccessor scopeAccessor) { _scopeAccessor = scopeAccessor; } /// /// Gets a page of items used in any kind of relation from selected integer ids. /// public IEnumerable GetPagedItemsWithRelations(int[] ids, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[pn].[id] as nodeId", "[pn].[uniqueId] as nodeKey", "[pn].[text] as nodeName", "[pn].[nodeObjectType] as nodeObjectType", "[ct].[icon] as contentTypeIcon", "[ct].[alias] as contentTypeAlias", "[ctn].[text] as contentTypeName", "[umbracoRelationType].[alias] as relationTypeAlias", "[umbracoRelationType].[name] as relationTypeName", "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", 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 (ids.Any()) { sql = sql.Where(x => ids.Contains(x.NodeId), "pn"); } if (filterMustBeIsDependency) { sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); } // Ordering is required for paging sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } /// /// Gets a page of the descending items that have any references, given a parent id. /// public IEnumerable GetPagedDescendantsInReferences(int parentId, long pageIndex, int pageSize, 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.NodeId == parentId, "node"); // Gets the descendants of the parent node Sql subQuery; if (_scopeAccessor.AmbientScope.Database.DatabaseType.IsSqlCe()) { // SqlCE does not support nested selects that returns a scalar. So we need to do this in multiple queries var pathForLike = _scopeAccessor.AmbientScope.Database.ExecuteScalar(subsubQuery); subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select(x => x.NodeId) .From() .WhereLike(x => x.Path, pathForLike); } else { subQuery = _scopeAccessor.AmbientScope.Database.SqlContext.Sql() .Select(x => x.NodeId) .From() .WhereLike(x => x.Path, subsubQuery); } // Get all relations where parent is in the sub query var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[pn].[id] as nodeId", "[pn].[uniqueId] as nodeKey", "[pn].[text] as nodeName", "[pn].[nodeObjectType] as nodeObjectType", "[ct].[icon] as contentTypeIcon", "[ct].[alias] as contentTypeAlias", "[ctn].[text] as contentTypeName", "[umbracoRelationType].[alias] as relationTypeAlias", "[umbracoRelationType].[name] as relationTypeName", "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "pn", 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") .WhereIn((System.Linq.Expressions.Expression>)(x => x.NodeId), subQuery, "pn"); if (filterMustBeIsDependency) { sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); } // Ordering is required for paging sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } /// /// Gets a page of items which are in relation with the current item. /// Basically, shows the items which depend on the current item. /// public IEnumerable GetPagedRelationsForItem(int id, long pageIndex, int pageSize, bool filterMustBeIsDependency, out long totalRecords) { var sql = _scopeAccessor.AmbientScope.Database.SqlContext.Sql().SelectDistinct( "[cn].[id] as nodeId", "[cn].[uniqueId] as nodeKey", "[cn].[text] as nodeName", "[cn].[nodeObjectType] as nodeObjectType", "[ct].[icon] as contentTypeIcon", "[ct].[alias] as contentTypeAlias", "[ctn].[text] as contentTypeName", "[umbracoRelationType].[alias] as relationTypeAlias", "[umbracoRelationType].[name] as relationTypeName", "[umbracoRelationType].[isDependency] as relationTypeIsDependency", "[umbracoRelationType].[dual] as relationTypeIsBidirectional") .From("r") .InnerJoin("umbracoRelationType").On((left, right) => left.RelationType == right.Id, aliasLeft: "r", aliasRight: "umbracoRelationType") .InnerJoin("cn").On((r, cn, rt) => (!rt.Dual && r.ParentId == cn.NodeId) || (rt.Dual && (r.ChildId == cn.NodeId || r.ParentId == cn.NodeId)), aliasLeft: "r", aliasRight: "cn", aliasOther: "umbracoRelationType") .InnerJoin("pn").On((r, pn, cn) => (pn.NodeId == r.ChildId && cn.NodeId == r.ParentId) || (pn.NodeId == r.ParentId && cn.NodeId == r.ChildId), aliasLeft: "r", aliasRight: "pn", aliasOther: "cn") .LeftJoin("c").On((left, right) => left.NodeId == right.NodeId, aliasLeft: "cn", 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") .Where(x => x.NodeId == id, "pn") .Where(x => x.ChildId == id || x.ParentId == id, "r"); // This last Where is purely to help SqlServer make a smarter query plan. More info https://github.com/umbraco/Umbraco-CMS/issues/12190 if (filterMustBeIsDependency) { sql = sql.Where(rt => rt.IsDependency, "umbracoRelationType"); } // Ordering is required for paging sql = sql.OrderBy(x => x.Alias); var pagedResult = _scopeAccessor.AmbientScope.Database.Page(pageIndex + 1, pageSize, sql); totalRecords = pagedResult.TotalItems; return pagedResult.Items.Select(MapDtoToEntity); } private RelationItem MapDtoToEntity(RelationItemDto dto) { return new RelationItem() { NodeId = dto.ChildNodeId, NodeKey = dto.ChildNodeKey, NodeType = ObjectTypes.GetUdiType(dto.ChildNodeObjectType), NodeName = dto.ChildNodeName, RelationTypeName = dto.RelationTypeName, RelationTypeIsBidirectional = dto.RelationTypeIsBidirectional, RelationTypeIsDependency = dto.RelationTypeIsDependency, ContentTypeAlias = dto.ChildContentTypeAlias, ContentTypeIcon = dto.ChildContentTypeIcon, ContentTypeName = dto.ChildContentTypeName, }; } } }