using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Scoping; namespace Umbraco.Core.Services.Implement { public class EntityService : ScopeRepositoryService, IEntityService { private readonly IEntityRepository _entityRepository; private readonly Dictionary GetById, Func GetByKey)> _objectTypes; private IQuery _queryRootEntity; private readonly IdkMap _idkMap; public EntityService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, IDataTypeService dataTypeService, IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap, IEntityRepository entityRepository) : base(provider, logger, eventMessagesFactory) { _idkMap = idkMap; _entityRepository = entityRepository; _objectTypes = new Dictionary, Func)> { { typeof (IDataType).FullName, (UmbracoObjectTypes.DataType, dataTypeService.GetDataType, dataTypeService.GetDataType) }, { typeof (IContent).FullName, (UmbracoObjectTypes.Document, contentService.GetById, contentService.GetById) }, { typeof (IContentType).FullName, (UmbracoObjectTypes.DocumentType, contentTypeService.Get, contentTypeService.Get) }, { typeof (IMedia).FullName, (UmbracoObjectTypes.Media, mediaService.GetById, mediaService.GetById) }, { typeof (IMediaType).FullName, (UmbracoObjectTypes.MediaType, mediaTypeService.Get, mediaTypeService.Get) }, { typeof (IMember).FullName, (UmbracoObjectTypes.Member, memberService.GetById, memberService.GetByKey) }, { typeof (IMemberType).FullName, (UmbracoObjectTypes.MemberType, memberTypeService.Get, memberTypeService.Get) }, }; } #region Static Queries // lazy-constructed because when the ctor runs, the query factory may not be ready private IQuery QueryRootEntity => _queryRootEntity ?? (_queryRootEntity = Query().Where(x => x.ParentId == -1)); #endregion // gets the getters, throws if not supported private (UmbracoObjectTypes ObjectType, Func GetById, Func GetByKey) GetGetters(Type type) { if (type?.FullName == null || !_objectTypes.TryGetValue(type.FullName, out var getters)) throw new NotSupportedException($"Type \"{type?.FullName ?? ""}\" is not supported here."); return getters; } /// public IEntitySlim Get(int id) { return (IEntitySlim) Get(id, false); } /// public IUmbracoEntity Get(int id, bool full) { if (!full) { // get the light entity using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Get(id); } } // get the full entity var objectType = GetObjectType(id); var entityType = objectType.GetClrType(); var getters = GetGetters(entityType); return getters.GetById(id); } /// public IEntitySlim Get(Guid key) { return (IEntitySlim) Get(key, false); } /// public IUmbracoEntity Get(Guid key, bool full) { if (!full) { // get the light entity using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Get(key); } } // get the full entity var objectType = GetObjectType(key); var entityType = objectType.GetClrType(); var getters = GetGetters(entityType); return getters.GetByKey(key); } /// public virtual IEntitySlim Get(int id, UmbracoObjectTypes objectType) { return (IEntitySlim) Get(id, objectType, false); } /// public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes objectType, bool full) { if (!full) { // get the light entity using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Get(id, objectType.GetGuid()); } } // get the full entity var entityType = objectType.GetClrType(); var getters = GetGetters(entityType); return getters.GetById(id); } /// public IEntitySlim Get(Guid key, UmbracoObjectTypes objectType) { return (IEntitySlim) Get(key, objectType, false); } /// public IUmbracoEntity Get(Guid key, UmbracoObjectTypes objectType, bool full) { if (!full) { // get the light entity using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Get(key, objectType.GetGuid()); } } // get the full entity var entityType = objectType.GetClrType(); var getters = GetGetters(entityType); return getters.GetByKey(key); } /// public virtual IEntitySlim Get(int id) where T : IUmbracoEntity { return (IEntitySlim) Get(id, false); } /// public virtual IUmbracoEntity Get(int id, bool full) where T : IUmbracoEntity { if (!full) { // get the light entity using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Get(id); } } // get the full entity var entityType = typeof (T); var getters = GetGetters(entityType); return getters.GetById(id); } /// public virtual IEntitySlim Get(Guid key) where T : IUmbracoEntity { return (IEntitySlim) Get(key, false); } /// public IUmbracoEntity Get(Guid key, bool full) where T : IUmbracoEntity { if (!full) { // get the light entity using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Get(key); } } // get the full entity var entityType = typeof (T); var getters = GetGetters(entityType); return getters.GetByKey(key); } /// public bool Exists(int id) { using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Exists(id); } } /// public bool Exists(Guid key) { using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.Exists(key); } } /// public virtual IEnumerable GetAll() where T : IUmbracoEntity => GetAll(Array.Empty()); /// public virtual IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity { var entityType = typeof (T); var getters = GetGetters(entityType); var objectType = getters.ObjectType; var objectTypeId = objectType.GetGuid(); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAll(objectTypeId, ids); } } /// public virtual IEnumerable GetAll(UmbracoObjectTypes objectType) => GetAll(objectType, Array.Empty()); /// public virtual IEnumerable GetAll(UmbracoObjectTypes objectType, params int[] ids) { var entityType = objectType.GetClrType(); GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAll(objectType.GetGuid(), ids); } } /// public virtual IEnumerable GetAll(Guid objectType) => GetAll(objectType, Array.Empty()); /// public virtual IEnumerable GetAll(Guid objectType, params int[] ids) { var entityType = ObjectTypes.GetClrType(objectType); GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAll(objectType, ids); } } /// public virtual IEnumerable GetAll(params Guid[] keys) where T : IUmbracoEntity { var entityType = typeof (T); var getters = GetGetters(entityType); var objectType = getters.ObjectType; var objectTypeId = objectType.GetGuid(); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAll(objectTypeId, keys); } } /// public IEnumerable GetAll(UmbracoObjectTypes objectType, Guid[] keys) { var entityType = objectType.GetClrType(); GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAll(objectType.GetGuid(), keys); } } /// public virtual IEnumerable GetAll(Guid objectType, params Guid[] keys) { var entityType = ObjectTypes.GetClrType(objectType); GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAll(objectType, keys); } } /// public virtual IEnumerable GetRootEntities(UmbracoObjectTypes objectType) { using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetByQuery(QueryRootEntity, objectType.GetGuid()); } } /// public virtual IEntitySlim GetParent(int id) { using (ScopeProvider.CreateScope(autoComplete: true)) { var entity = _entityRepository.Get(id); if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; return _entityRepository.Get(entity.ParentId); } } /// public virtual IEntitySlim GetParent(int id, UmbracoObjectTypes objectType) { using (ScopeProvider.CreateScope(autoComplete: true)) { var entity = _entityRepository.Get(id); if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; return _entityRepository.Get(entity.ParentId, objectType.GetGuid()); } } /// public virtual IEnumerable GetChildren(int parentId) { using (ScopeProvider.CreateScope(autoComplete: true)) { var query = Query().Where(x => x.ParentId == parentId); return _entityRepository.GetByQuery(query); } } /// public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes objectType) { using (ScopeProvider.CreateScope(autoComplete: true)) { var query = Query().Where(x => x.ParentId == parentId); return _entityRepository.GetByQuery(query, objectType.GetGuid()); } } /// public virtual IEnumerable GetDescendants(int id) { using (ScopeProvider.CreateScope(autoComplete: true)) { var entity = _entityRepository.Get(id); var pathMatch = entity.Path + ","; var query = Query().Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); return _entityRepository.GetByQuery(query); } } /// public virtual IEnumerable GetDescendants(int id, UmbracoObjectTypes objectType) { using (ScopeProvider.CreateScope(autoComplete: true)) { var entity = _entityRepository.Get(id); var query = Query().Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); return _entityRepository.GetByQuery(query, objectType.GetGuid()); } } /// public IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") { using (ScopeProvider.CreateScope(autoComplete: true)) { var query = Query().Where(x => x.ParentId == id && x.Trashed == false); var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); } } /// public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { using (ScopeProvider.CreateScope(autoComplete: true)) { var objectTypeGuid = objectType.GetGuid(); var query = Query(); if (id != Constants.System.Root) { // lookup the path so we can use it in the prefix query below var paths = _entityRepository.GetAllPaths(objectTypeGuid, id).ToArray(); if (paths.Length == 0) { totalRecords = 0; return Enumerable.Empty(); } var path = paths[0].Path; query.Where(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar)); } var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); } } /// public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { totalRecords = 0; var idsA = ids.ToArray(); if (idsA.Length == 0) return Enumerable.Empty(); using (ScopeProvider.CreateScope(autoComplete: true)) { var objectTypeGuid = objectType.GetGuid(); var query = Query(); if (idsA.All(x => x != Constants.System.Root)) { var paths = _entityRepository.GetAllPaths(objectTypeGuid, idsA).ToArray(); if (paths.Length == 0) { totalRecords = 0; return Enumerable.Empty(); } var clauses = new List>>(); foreach (var id in idsA) { // if the id is root then don't add any clauses if (id == Constants.System.Root) continue; var entityPath = paths.FirstOrDefault(x => x.Id == id); if (entityPath == null) continue; var path = entityPath.Path; var qid = id; clauses.Add(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar) || x.Path.SqlEndsWith("," + qid, TextColumnType.NVarchar)); } query.WhereAny(clauses); } var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); } } /// public IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) { using (ScopeProvider.CreateScope(autoComplete: true)) { var query = Query(); if (includeTrashed == false) query.Where(x => x.Trashed == false); var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); } } /// public virtual UmbracoObjectTypes GetObjectType(int id) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { var sql = scope.SqlContext.Sql() .Select(x => x.NodeObjectType) .From() .Where(x => x.NodeId == id); var guid = scope.Database.ExecuteScalar(sql); return ObjectTypes.GetUmbracoObjectType(guid); } } /// public virtual UmbracoObjectTypes GetObjectType(Guid key) { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { var sql = scope.SqlContext.Sql() .Select(x => x.NodeObjectType) .From() .Where(x => x.UniqueId == key); var guid = scope.Database.ExecuteScalar(sql); return ObjectTypes.GetUmbracoObjectType(guid); } } /// public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) { return entity is IEntitySlim light ? ObjectTypes.GetUmbracoObjectType(light.NodeObjectType) : GetObjectType(entity.Id); } /// public virtual Type GetEntityType(int id) { var objectType = GetObjectType(id); return objectType.GetClrType(); } /// public Attempt GetId(Guid key, UmbracoObjectTypes objectType) { return _idkMap.GetIdForKey(key, objectType); } /// public Attempt GetId(Udi udi) { return _idkMap.GetIdForUdi(udi); } /// public Attempt GetKey(int id, UmbracoObjectTypes umbracoObjectType) { return _idkMap.GetKeyForId(id, umbracoObjectType); } /// public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params int[] ids) { var entityType = objectType.GetClrType(); GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAllPaths(objectType.GetGuid(), ids); } } /// public virtual IEnumerable GetAllPaths(UmbracoObjectTypes objectType, params Guid[] keys) { var entityType = objectType.GetClrType(); GetGetters(entityType); using (ScopeProvider.CreateScope(autoComplete: true)) { return _entityRepository.GetAllPaths(objectType.GetGuid(), keys); } } /// public int ReserveId(Guid key) { NodeDto node; using (var scope = ScopeProvider.CreateScope()) { var sql = scope.SqlContext.Sql() .Select() .From() .Where(x => x.UniqueId == key && x.NodeObjectType == Constants.ObjectTypes.IdReservation); node = scope.Database.SingleOrDefault(sql); if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi."); node = new NodeDto { UniqueId = key, Text = "RESERVED.ID", NodeObjectType = Constants.ObjectTypes.IdReservation, CreateDate = DateTime.Now, UserId = -1, ParentId = -1, Level = 1, Path = "-1", SortOrder = 0, Trashed = false }; scope.Database.Insert(node); scope.Complete(); } return node.NodeId; } } }