using System; using System.Collections.Generic; using System.Linq; using NPoco; using System.Linq.Expressions; using System.Text; using Umbraco.Core.Cache; using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Services { public class EntityService : ScopeRepositoryService, IEntityService { private readonly Dictionary>> _supportedObjectTypes; private IQuery _queryRootEntity; private readonly IdkMap _idkMap; public EntityService(IScopeUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IContentTypeService contentTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, IDataTypeService dataTypeService, IMemberService memberService, IMemberTypeService memberTypeService, IdkMap idkMap) IRuntimeCacheProvider runtimeCache) : base(provider, logger, eventMessagesFactory) { _idkMap = idkMap; _supportedObjectTypes = new Dictionary>> { {typeof (IDataTypeDefinition).FullName, new Tuple>(UmbracoObjectTypes.DataType, dataTypeService.GetDataTypeDefinitionById)}, {typeof (IContent).FullName, new Tuple>(UmbracoObjectTypes.Document, contentService.GetById)}, {typeof (IContentType).FullName, new Tuple>(UmbracoObjectTypes.DocumentType, contentTypeService.Get)}, {typeof (IMedia).FullName, new Tuple>(UmbracoObjectTypes.Media, mediaService.GetById)}, {typeof (IMediaType).FullName, new Tuple>(UmbracoObjectTypes.MediaType, mediaTypeService.Get)}, {typeof (IMember).FullName, new Tuple>(UmbracoObjectTypes.Member, memberService.GetById)}, {typeof (IMemberType).FullName, new Tuple>(UmbracoObjectTypes.MemberType, memberTypeService.Get)}, }; } #region Static Queries // lazy-constructed because when the ctor runs, the query factory may not be ready private IQuery QueryRootEntity => _queryRootEntity ?? (_queryRootEntity = UowProvider.DatabaseContext.Query().Where(x => x.ParentId == -1)); #endregion /// /// Returns the integer id for a given GUID /// /// /// /// public Attempt GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType) { return _idkMap.GetIdForKey(key, umbracoObjectType); } public Attempt GetIdForUdi(Udi udi) { return _idkMap.GetIdForUdi(udi); } /// /// Returns the GUID for a given integer id /// /// /// /// public Attempt GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType) { return _idkMap.GetKeyForId(id, umbracoObjectType); } public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) { if (loadBaseType) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetByKey(key); } } //SD: TODO: Need to enable this at some stage ... just need to ask Morten what the deal is with what this does. throw new NotSupportedException(); //var objectType = GetObjectType(key); //var entityType = GetEntityType(objectType); //var typeFullName = entityType.FullName; //var entity = _supportedObjectTypes[typeFullName].Item2(id); //return entity; } /// /// Gets an UmbracoEntity by its Id, and optionally loads the complete object graph. /// /// /// By default this will load the base type with a minimum set of properties. /// /// Id of the object to retrieve /// Optional bool to load the complete object graph when set to False. /// An public virtual IUmbracoEntity Get(int id, bool loadBaseType = true) { if (loadBaseType) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.Get(id); } } var objectType = GetObjectType(id); var entityType = GetEntityType(objectType); var typeFullName = entityType.FullName; var entity = _supportedObjectTypes[typeFullName].Item2(id); return entity; } public IUmbracoEntity GetByKey(Guid key, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) { if (loadBaseType) { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetByKey(key, objectTypeId); } } //SD: TODO: Need to enable this at some stage ... just need to ask Morten what the deal is with what this does. throw new NotSupportedException(); //var entityType = GetEntityType(umbracoObjectType); //var typeFullName = entityType.FullName; //var entity = _supportedObjectTypes[typeFullName].Item2(id); //return entity; } /// /// Gets an UmbracoEntity by its Id and UmbracoObjectType, and optionally loads the complete object graph. /// /// /// By default this will load the base type with a minimum set of properties. /// /// Id of the object to retrieve /// UmbracoObjectType of the entity to retrieve /// Optional bool to load the complete object graph when set to False. /// An public virtual IUmbracoEntity Get(int id, UmbracoObjectTypes umbracoObjectType, bool loadBaseType = true) { if (loadBaseType) { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.Get(id, objectTypeId); } } var entityType = GetEntityType(umbracoObjectType); var typeFullName = entityType.FullName; var entity = _supportedObjectTypes[typeFullName].Item2(id); return entity; } public IUmbracoEntity GetByKey(Guid key, bool loadBaseType = true) where T : IUmbracoEntity { throw new NotImplementedException(); } /// /// Gets an UmbracoEntity by its Id and specified Type. Optionally loads the complete object graph. /// /// /// By default this will load the base type with a minimum set of properties. /// /// Type of the model to retrieve. Must be based on an /// Id of the object to retrieve /// Optional bool to load the complete object graph when set to False. /// An public virtual IUmbracoEntity Get(int id, bool loadBaseType = true) where T : IUmbracoEntity { if (loadBaseType) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.Get(id); } } var typeFullName = typeof(T).FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported"); var entity = _supportedObjectTypes[typeFullName].Item2(id); return entity; } /// /// Gets the parent of entity by its id /// /// Id of the entity to retrieve the Parent for /// An public virtual IUmbracoEntity GetParent(int id) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var entity = repository.Get(id); if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; return repository.Get(entity.ParentId); } } /// /// Gets the parent of entity by its id and UmbracoObjectType /// /// Id of the entity to retrieve the Parent for /// UmbracoObjectType of the parent to retrieve /// An public virtual IUmbracoEntity GetParent(int id, UmbracoObjectTypes umbracoObjectType) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var entity = repository.Get(id); if (entity.ParentId == -1 || entity.ParentId == -20 || entity.ParentId == -21) return null; var objectTypeId = umbracoObjectType.GetGuid(); return repository.Get(entity.ParentId, objectTypeId); } } /// /// Gets a collection of children by the parents Id /// /// Id of the parent to retrieve children for /// An enumerable list of objects public virtual IEnumerable GetChildren(int parentId) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == parentId); return repository.GetByQuery(query); } } /// /// Gets a collection of children by the parents Id and UmbracoObjectType /// /// Id of the parent to retrieve children for /// UmbracoObjectType of the children to retrieve /// An enumerable list of objects public virtual IEnumerable GetChildren(int parentId, UmbracoObjectTypes umbracoObjectType) { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == parentId); return repository.GetByQuery(query, objectTypeId).ToList(); // run within using! } } /// /// Gets a collection of descendents by the parents Id /// /// Id of entity to retrieve descendents for /// An enumerable list of objects public virtual IEnumerable GetDescendents(int id) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var entity = repository.Get(id); var pathMatch = entity.Path + ","; var query = repository.Query.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id); return repository.GetByQuery(query); } } /// /// Gets a collection of descendents by the parents Id /// /// Id of entity to retrieve descendents for /// UmbracoObjectType of the descendents to retrieve /// An enumerable list of objects public virtual IEnumerable GetDescendents(int id, UmbracoObjectTypes umbracoObjectType) { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var entity = repository.Get(id); var query = repository.Query.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); return repository.GetByQuery(query, objectTypeId); } } /// /// Returns a paged collection of children /// /// The parent id to return children for /// /// /// /// /// /// /// /// public IEnumerable GetPagedChildren(int parentId, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ParentId == parentId && x.Trashed == false); IQuery filterQuery = null; if (filter.IsNullOrWhiteSpace() == false) { filterQuery = repository.Query.Where(x => x.Name.Contains(filter)); } var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); return contents; } } /// /// Returns a paged collection of descendants /// /// /// /// /// /// /// /// /// /// public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var query = repository.Query; //if the id is System Root, then just get all if (id != Constants.System.Root) query.Where(x => x.Path.SqlContains($",{id},", TextColumnType.NVarchar)); IQuery filterQuery = null; if (filter.IsNullOrWhiteSpace() == false) { filterQuery = uow.Query().Where(x => x.Name.Contains(filter)); } var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); return contents; } } /// /// Returns a paged collection of descendants. /// public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes umbracoObjectType, 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(); var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var query = uow.Query(); if (idsA.All(x => x != Constants.System.Root)) { var clauses = new List>>(); foreach (var id in idsA) { var qid = id; clauses.Add(x => x.Path.SqlContains(string.Format(",{0},", qid), TextColumnType.NVarchar) || x.Path.SqlEndsWith(string.Format(",{0}", qid), TextColumnType.NVarchar)); } query.WhereAny(clauses); } IQuery filterQuery = null; if (filter.IsNullOrWhiteSpace() == false) { filterQuery = uow.Query().Where(x => x.Name.Contains(filter)); } var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); return contents; } } /// /// Returns a paged collection of descendants from the root /// /// /// /// /// /// /// /// /// true/false to include trashed objects /// public IEnumerable GetPagedDescendantsFromRoot(UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var query = repository.Query; //don't include trashed if specfied if (includeTrashed == false) { query.Where(x => x.Trashed == false); } IQuery filterQuery = null; if (filter.IsNullOrWhiteSpace() == false) { filterQuery = repository.Query.Where(x => x.Name.Contains(filter)); } var contents = repository.GetPagedResultsByQuery(query, objectTypeId, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); return contents; } } /// /// Gets a collection of the entities at the root, which corresponds to the entities with a Parent Id of -1. /// /// UmbracoObjectType of the root entities to retrieve /// An enumerable list of objects public virtual IEnumerable GetRootEntities(UmbracoObjectTypes umbracoObjectType) { var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetByQuery(QueryRootEntity, objectTypeId); } } /// /// Gets a collection of all of a given type. /// /// Type of the entities to retrieve /// An enumerable list of objects public virtual IEnumerable GetAll(params int[] ids) where T : IUmbracoEntity { var typeFullName = typeof(T).FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported"); var objectType = _supportedObjectTypes[typeFullName].Item1; return GetAll(objectType, ids); } /// /// Gets a collection of all of a given type. /// /// UmbracoObjectType of the entities to return /// /// An enumerable list of objects public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, params int[] ids) { var entityType = GetEntityType(umbracoObjectType); var typeFullName = entityType.FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported"); var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetAll(objectTypeId, ids); } } public IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType, Guid[] keys) { var entityType = GetEntityType(umbracoObjectType); var typeFullName = entityType.FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported"); var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetAll(objectTypeId, keys); } } public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params int[] ids) { var entityType = GetEntityType(umbracoObjectType); var typeFullName = entityType.FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported.); var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetAllPaths(objectTypeId, ids); } } public virtual IEnumerable GetAllPaths(UmbracoObjectTypes umbracoObjectType, params Guid[] keys) { var entityType = GetEntityType(umbracoObjectType); var typeFullName = entityType.FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported.); var objectTypeId = umbracoObjectType.GetGuid(); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetAllPaths(objectTypeId, keys); } } /// /// Gets a collection of /// /// Guid id of the UmbracoObjectType /// /// An enumerable list of objects public virtual IEnumerable GetAll(Guid objectTypeId, params int[] ids) { var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); var entityType = GetEntityType(umbracoObjectType); var typeFullName = entityType.FullName; if (_supportedObjectTypes.ContainsKey(typeFullName) == false) throw new NotSupportedException("The passed in type is not supported"); using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); return repository.GetAll(objectTypeId, ids); } } /// /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. /// /// Id of the entity /// public virtual UmbracoObjectTypes GetObjectType(int id) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var sql = uow.Sql() .Select("nodeObjectType") .From() .Where(x => x.NodeId == id); var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); } } /// /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. /// /// Unique Id of the entity /// public virtual UmbracoObjectTypes GetObjectType(Guid key) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var sql = uow.Sql() .Select("nodeObjectType") .From() .Where(x => x.UniqueId == key); var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); var objectTypeId = nodeObjectTypeId; return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); } } /// /// Gets the UmbracoObjectType from an IUmbracoEntity. /// /// /// public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) { var entityImpl = entity as UmbracoEntity; return entityImpl == null ? GetObjectType(entity.Id) : UmbracoObjectTypesExtensions.GetUmbracoObjectType(entityImpl.NodeObjectTypeId); } /// /// Gets the Type of an entity by its Id /// /// Id of the entity /// Type of the entity public virtual Type GetEntityType(int id) { var objectType = GetObjectType(id); return GetEntityType(objectType); } /// /// Gets the Type of an entity by its /// /// /// Type of the entity public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) { var type = typeof(UmbracoObjectTypes); var memInfo = type.GetMember(umbracoObjectType.ToString()); var attributes = memInfo[0].GetCustomAttributes(typeof(UmbracoObjectTypeAttribute), false); var attribute = ((UmbracoObjectTypeAttribute)attributes[0]); if (attribute == null) throw new NullReferenceException("The passed in UmbracoObjectType does not contain an UmbracoObjectTypeAttribute, which is used to retrieve the Type."); if (attribute.ModelType == null) throw new NullReferenceException("The passed in UmbracoObjectType does not contain a Type definition"); return attribute.ModelType; } public bool Exists(Guid key) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var exists = repository.Exists(key); return exists; } } public bool Exists(int id) { using (var uow = UowProvider.CreateUnitOfWork(readOnly: true)) { var repository = uow.CreateRepository(); var exists = repository.Exists(id); return exists; } } } }