diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs index af4476f394..84067fabd7 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypesExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.CodeAnnotations; namespace Umbraco.Core.Models @@ -8,6 +9,8 @@ namespace Umbraco.Core.Models /// public static class UmbracoObjectTypesExtensions { + private static readonly Dictionary UmbracoObjectTypeCache = new Dictionary(); + /// /// Get an UmbracoObjectTypes value from it's name /// @@ -45,10 +48,15 @@ namespace Umbraco.Core.Models /// a GUID value of the UmbracoObjectTypes public static Guid GetGuid(this UmbracoObjectTypes umbracoObjectType) { + if (UmbracoObjectTypeCache.ContainsKey(umbracoObjectType)) + return UmbracoObjectTypeCache[umbracoObjectType]; + var attribute = umbracoObjectType.GetType().FirstAttribute(); if (attribute == null) return Guid.Empty; + UmbracoObjectTypeCache.Add(umbracoObjectType, attribute.ObjectId); + return attribute.ObjectId; } diff --git a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs index 41ce480a44..e07110809c 100644 --- a/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UmbracoEntityFactory.cs @@ -1,12 +1,12 @@ using System.Globalization; using Umbraco.Core.Models; -using Umbraco.Core.Models.Rdbms; +using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Core.Persistence.Factories { - internal class UmbracoEntityFactory : IEntityFactory + internal class UmbracoEntityFactory : IEntityFactory { - public UmbracoEntity BuildEntity(NodeDto dto) + public UmbracoEntity BuildEntity(EntityRepository.UmbracoEntityDto dto) { var entity = new UmbracoEntity(dto.Trashed) { @@ -20,15 +20,15 @@ namespace Umbraco.Core.Persistence.Factories ParentId = dto.ParentId, Path = dto.Path, SortOrder = dto.SortOrder, - HasChildren = false, - IsPublished = false + HasChildren = dto.Children > 0, + IsPublished = dto.HasPublishedVersion.HasValue && dto.HasPublishedVersion.Value > 0 }; return entity; } - public NodeDto BuildDto(UmbracoEntity entity) + public EntityRepository.UmbracoEntityDto BuildDto(UmbracoEntity entity) { - var node = new NodeDto + var node = new EntityRepository.UmbracoEntityDto { CreateDate = entity.CreateDate, Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)), diff --git a/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs new file mode 100644 index 0000000000..d1abd4043f --- /dev/null +++ b/src/Umbraco.Core/Persistence/Mappers/UmbracoEntityMapper.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; + +namespace Umbraco.Core.Persistence.Mappers +{ + [MapperFor(typeof (IUmbracoEntity))] + public sealed class UmbracoEntityMapper : BaseMapper + { + private static readonly ConcurrentDictionary PropertyInfoCache = + new ConcurrentDictionary(); + + //NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it + // otherwise that would fail because there is no public constructor. + public UmbracoEntityMapper() + { + BuildMap(); + } + + #region Overrides of BaseMapper + + internal override void BuildMap() + { + CacheMap(src => src.Id, dto => dto.NodeId); + CacheMap(src => src.CreateDate, dto => dto.CreateDate); + CacheMap(src => src.Level, dto => dto.Level); + CacheMap(src => src.ParentId, dto => dto.ParentId); + CacheMap(src => src.Path, dto => dto.Path); + CacheMap(src => src.SortOrder, dto => dto.SortOrder); + CacheMap(src => src.Name, dto => dto.Text); + CacheMap(src => src.Trashed, dto => dto.Trashed); + CacheMap(src => src.Key, dto => dto.UniqueId); + CacheMap(src => src.CreatorId, dto => dto.UserId); + } + + internal override string Map(string propertyName) + { + if (!PropertyInfoCache.ContainsKey(propertyName)) + return string.Empty; + + var dtoTypeProperty = PropertyInfoCache[propertyName]; + + return base.GetColumnName(dtoTypeProperty.Type, dtoTypeProperty.PropertyInfo); + } + + internal override void CacheMap(Expression> sourceMember, + Expression> destinationMember) + { + var property = base.ResolveMapping(sourceMember, destinationMember); + PropertyInfoCache.AddOrUpdate(property.SourcePropertyName, property, (x, y) => property); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index d63d48a43d..6d80670c89 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -19,18 +19,10 @@ namespace Umbraco.Core.Persistence.Repositories internal class EntityRepository : DisposableObject, IEntityRepository { private readonly IDatabaseUnitOfWork _work; - private readonly IDictionary> _propertyHandler; public EntityRepository(IDatabaseUnitOfWork work) { _work = work; - _propertyHandler = new Dictionary> - { - { - new Guid(Constants.ObjectTypes.Document), - UpdateIsPublished - } - }; } /// @@ -53,35 +45,28 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IUmbracoEntity Get(int id) { - var sql = GetBaseWhere(id); - var nodeDto = _work.Database.FirstOrDefault(sql); + var sql = GetBaseWhere(GetBase, false, id); + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); var entity = factory.BuildEntity(nodeDto); - - //TODO Update HasChildren and IsPublished - if (_propertyHandler.ContainsKey(entity.NodeObjectTypeId)) - { - return _propertyHandler[entity.NodeObjectTypeId](entity); - } return entity; } public virtual IUmbracoEntity Get(int id, Guid objectTypeId) { - var sql = GetBaseWhere(objectTypeId, id); - var nodeDto = _work.Database.FirstOrDefault(sql); + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var sql = GetBaseWhere(GetBase, isContent, objectTypeId, id).Append(GetGroupBy()); + var nodeDto = _work.Database.FirstOrDefault(sql); if (nodeDto == null) return null; var factory = new UmbracoEntityFactory(); var entity = factory.BuildEntity(nodeDto); - //TODO Update HasChildren and IsPublished - return entity; } @@ -96,15 +81,15 @@ namespace Umbraco.Core.Persistence.Repositories } else { - var sql = GetBaseWhere(objectTypeId); - var dtos = _work.Database.Fetch(sql); + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var sql = GetBaseWhere(GetBase, isContent, objectTypeId).Append(GetGroupBy()); + var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); foreach (var dto in dtos) { var entity = factory.BuildEntity(dto); - //TODO Update HasChildren and IsPublished properties yield return entity; } } @@ -112,80 +97,102 @@ namespace Umbraco.Core.Persistence.Repositories public virtual IEnumerable GetByQuery(IQuery query) { - var sqlClause = GetBaseQuery(); + var sqlClause = GetBase(false); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); + var sql = translator.Translate().Append(GetGroupBy()); - var dtos = _work.Database.Fetch(sql); + var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); var list = dtos.Select(factory.BuildEntity).Cast().ToList(); - //TODO Update HasChildren and IsPublished properties - return list; } public virtual IEnumerable GetByQuery(IQuery query, Guid objectTypeId) { - var sqlClause = GetBaseWhere(objectTypeId); + bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document); + var sqlClause = GetBaseWhere(GetBase, isContent, objectTypeId); var translator = new SqlTranslator(sqlClause, query); - var sql = translator.Translate(); + var sql = translator.Translate().Append(GetGroupBy()); - var dtos = _work.Database.Fetch(sql); + var dtos = _work.Database.Fetch(sql); var factory = new UmbracoEntityFactory(); var list = dtos.Select(factory.BuildEntity).Cast().ToList(); - //TODO Update HasChildren and IsPublished properties - return list; } #endregion - private void UpdateHasChildren(IUmbracoEntity entity) - {} - - private IUmbracoEntity UpdateIsPublished(IUmbracoEntity entity) - { - var umbracoEntity = entity as UmbracoEntity; - if (umbracoEntity != null) - { - umbracoEntity.IsPublished = true; - return umbracoEntity; - } - - return entity; - } - #region Sql Statements - protected virtual Sql GetBaseQuery() + protected virtual Sql GetBase(bool isContent) + { + var columns = new List + { + "main.id", + "main.trashed", + "main.parentID", + "main.nodeUser", + "main.level", + "main.path", + "main.sortOrder", + "main.uniqueID", + "main.text", + "main.nodeObjectType", + "main.createDate", + "COUNT(parent.parentID) as children", + isContent + ? "SUM(CONVERT(int, document.published)) as published" + : "SUM(0) as published" + }; + + var sql = new Sql() + .Select(columns.ToArray()) + .From("FROM umbracoNode main") + .LeftJoin("umbracoNode parent").On("parent.parentID = main.id"); + + + //NOTE Should this account for newest = 1 ? Scenarios: unsaved, saved not published, published + + if (isContent) + sql.LeftJoin("LEFT JOIN cmsDocument document").On("document.nodeId = main.id"); + + return sql; + } + + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, Guid id) + { + var sql = baseQuery(isContent) + .Where("main.nodeObjectType = @NodeObjectType", new {NodeObjectType = id}); + return sql; + } + + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, int id) + { + var sql = baseQuery(isContent) + .Where("main.id = @Id", new {Id = id}) + .Append(GetGroupBy()); + return sql; + } + + protected virtual Sql GetBaseWhere(Func baseQuery, bool isContent, Guid objectId, int id) + { + var sql = baseQuery(isContent) + .Where("main.id = @Id AND main.nodeObjectType = @NodeObjectType", + new {Id = id, NodeObjectType = objectId}); + return sql; + } + + protected virtual Sql GetGroupBy() { var sql = new Sql() - .From(); - return sql; - } - - protected virtual Sql GetBaseWhere(Guid id) - { - var sql = GetBaseQuery() - .Where(x => x.NodeObjectType == id); - return sql; - } - - protected virtual Sql GetBaseWhere(int id) - { - var sql = GetBaseQuery() - .Where(x => x.NodeId == id); - return sql; - } - - protected virtual Sql GetBaseWhere(Guid objectId, int id) - { - var sql = GetBaseWhere(objectId) - .Where(x => x.NodeId == id); + .GroupBy("main.id", "main.trashed", "main.parentID", "main.nodeUser", "main.level", + "main.path", "main.sortOrder", "main.uniqueID", "main.text", + "main.nodeObjectType", "main.createDate") + .OrderBy("main.sortOrder"); return sql; } @@ -201,5 +208,19 @@ namespace Umbraco.Core.Persistence.Repositories { UnitOfWork.DisposeIfDisposable(); } + + #region umbracoNode POCO - Extends NodeDto + [TableName("umbracoNode")] + [PrimaryKey("id")] + [ExplicitColumns] + internal class UmbracoEntityDto : NodeDto + { + [Column("children")] + public int Children { get; set; } + + [Column("published")] + public int? HasPublishedVersion { get; set; } + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index a56d3a2f55..bfc0b2dd9f 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; +using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; @@ -13,18 +14,15 @@ namespace Umbraco.Core.Services { private readonly IDatabaseUnitOfWorkProvider _uowProvider; private readonly RepositoryFactory _repositoryFactory; - - public EntityService() - : this(new RepositoryFactory()) - { } - - public EntityService(RepositoryFactory repositoryFactory) - : this(new PetaPocoUnitOfWorkProvider(), repositoryFactory) - { } - - public EntityService(IDatabaseUnitOfWorkProvider provider) - : this(provider, new RepositoryFactory()) - { } + private readonly Dictionary _supportedObjectTypes = new Dictionary + { + {typeof(IDataTypeDefinition).FullName, UmbracoObjectTypes.DataType}, + {typeof(IContent).FullName, UmbracoObjectTypes.Document}, + {typeof(IContentType).FullName, UmbracoObjectTypes.DocumentType}, + {typeof(IMedia).FullName, UmbracoObjectTypes.Media}, + {typeof(IMediaType).FullName, UmbracoObjectTypes.MediaType}, + {typeof(ITemplate).FullName, UmbracoObjectTypes.Template} + }; public EntityService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory) { @@ -51,6 +49,34 @@ namespace Umbraco.Core.Services } } + var objectType = GetObjectType(id); + + //TODO Implementing loading from the various services + throw new NotImplementedException(); + } + + /// + /// 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 repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) + { + return repository.Get(id, objectTypeId); + } + } + + //TODO Implementing loading from the various services throw new NotImplementedException(); } @@ -75,15 +101,23 @@ namespace Umbraco.Core.Services } } + Mandate.That(_supportedObjectTypes.ContainsKey(typeof (T).FullName), () => + { + throw + new NotSupportedException + ("The passed in type is not supported"); + }); + var objectType = _supportedObjectTypes[typeof(T).FullName]; + //TODO Implementing loading from the various services throw new NotImplementedException(); } /// - /// + /// 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 repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) @@ -97,10 +131,29 @@ namespace Umbraco.Core.Services } /// - /// + /// 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 repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) + { + 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 id) { using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) @@ -113,10 +166,28 @@ namespace Umbraco.Core.Services } /// - /// + /// 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 id, UmbracoObjectTypes umbracoObjectType) + { + var objectTypeId = umbracoObjectType.GetGuid(); + using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var contents = repository.GetByQuery(query, objectTypeId); + + return contents; + } + } + + /// + /// 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 repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) @@ -130,13 +201,32 @@ namespace Umbraco.Core.Services } /// - /// + /// Gets a collection of descendents by the parents Id /// - /// - /// - public virtual IEnumerable GetRootEntities(UmbracoObjectTypes objectType) + /// 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 = objectType.GetGuid(); + var objectTypeId = umbracoObjectType.GetGuid(); + using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) + { + var entity = repository.Get(id); + var query = Query.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id); + var entities = repository.GetByQuery(query, objectTypeId); + + return entities; + } + } + + /// + /// 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 repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == -1); @@ -147,25 +237,30 @@ namespace Umbraco.Core.Services } /// - /// + /// Gets a collection of all of a given type. /// - /// - /// + /// Type of the entities to retrieve + /// An enumerable list of objects public virtual IEnumerable GetAll() where T : IUmbracoEntity { - //TODO Implement this so the type passed in is verified against types on UmbracoObjectTypes - //and then used to get all through the method below. - throw new NotImplementedException(); + Mandate.That(_supportedObjectTypes.ContainsKey(typeof(T).FullName), () => + { + throw new NotSupportedException + ("The passed in type is not supported"); + }); + var objectType = _supportedObjectTypes[typeof (T).FullName]; + + return GetAll(objectType); } /// - /// + /// Gets a collection of all of a given type. /// - /// - /// - public virtual IEnumerable GetAll(UmbracoObjectTypes objectType) + /// UmbracoObjectType of the entities to return + /// An enumerable list of objects + public virtual IEnumerable GetAll(UmbracoObjectTypes umbracoObjectType) { - var objectTypeId = objectType.GetGuid(); + var objectTypeId = umbracoObjectType.GetGuid(); using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) { return repository.GetAll(objectTypeId); @@ -173,10 +268,10 @@ namespace Umbraco.Core.Services } /// - /// + /// Gets a collection of /// - /// - /// + /// Guid id of the UmbracoObjectType + /// An enumerable list of objects public virtual IEnumerable GetAll(Guid objectTypeId) { using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork())) @@ -186,48 +281,61 @@ namespace Umbraco.Core.Services } /// - /// + /// Gets the UmbracoObjectType from the integer id of an IUmbracoEntity. /// - /// - /// + /// Id of the entity + /// public virtual UmbracoObjectTypes GetObjectType(int id) { - //TODO Implement so the entity is fetched from the db and then the Guid is used to resolve the UmbracoObjectType - throw new NotImplementedException(); + using (var uow = _uowProvider.GetUnitOfWork()) + { + var sql = new Sql().Select("nodeObjectType").From().Where(x => x.NodeId == id); + var nodeObjectTypeId = uow.Database.ExecuteScalar(sql); + var objectTypeId = new Guid(nodeObjectTypeId); + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(objectTypeId); + } } /// - /// + /// Gets the UmbracoObjectType from an IUmbracoEntity. /// - /// - /// + /// + /// public virtual UmbracoObjectTypes GetObjectType(IUmbracoEntity entity) { - //TODO Implement this so the entity is cast to UmbracoEntity - if valid get the guid id and then resolve the UmbracoObjectType - //otherwise fetch the IUmbracoEntity from the db and do it similar to above. - throw new NotImplementedException(); + var entityImpl = entity as UmbracoEntity; + if (entityImpl == null) + return GetObjectType(entity.Id); + + return UmbracoObjectTypesExtensions.GetUmbracoObjectType(entityImpl.NodeObjectTypeId); } /// - /// + /// Gets the Type of an entity by its Id /// - /// - /// - public virtual Type GetModelType(int id) + /// Id of the entity + /// Type of the entity + public virtual Type GetEntityType(int id) { - //TODO Implement so the IUmbracoEntity is fetched from the db and then used to resolve the real type, ie. IContent, IMedia etc. - throw new NotImplementedException(); + var objectType = GetObjectType(id); + return GetEntityType(objectType); } /// - /// + /// Gets the Type of an entity by its /// - /// - /// - public virtual Type GetModelType(UmbracoObjectTypes objectType) + /// + /// Type of the entity + public virtual Type GetEntityType(UmbracoObjectTypes umbracoObjectType) { - //TODO Implement so the real type is returned fro the UmbracoObjectType's attribute. - throw new NotImplementedException(); + var attribute = umbracoObjectType.GetType().FirstAttribute(); + 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; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 477e3696e8..95bedb1f6d 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -309,6 +309,7 @@ +