diff --git a/src/Umbraco.Abstractions/Constants-Conventions.cs b/src/Umbraco.Abstractions/Constants-Conventions.cs
index 40b06e37b2..37275b156a 100644
--- a/src/Umbraco.Abstractions/Constants-Conventions.cs
+++ b/src/Umbraco.Abstractions/Constants-Conventions.cs
@@ -225,34 +225,65 @@ namespace Umbraco.Core
public static class RelationTypes
{
///
- /// ContentType name for default relation type "Relate Document On Copy".
+ /// Name for default relation type "Related Media".
+ ///
+ public const string RelatedMediaName = "Related Media";
+
+ ///
+ /// Alias for default relation type "Related Media"
+ ///
+ public const string RelatedMediaAlias = "umbMedia";
+
+ ///
+ /// Name for default relation type "Related Document".
+ ///
+ public const string RelatedDocumentName = "Related Document";
+
+ ///
+ /// Alias for default relation type "Related Document"
+ ///
+ public const string RelatedDocumentAlias = "umbDocument";
+
+ ///
+ /// Name for default relation type "Relate Document On Copy".
///
public const string RelateDocumentOnCopyName = "Relate Document On Copy";
///
- /// ContentType alias for default relation type "Relate Document On Copy".
+ /// Alias for default relation type "Relate Document On Copy".
///
public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy";
///
- /// ContentType name for default relation type "Relate Parent Document On Delete".
+ /// Name for default relation type "Relate Parent Document On Delete".
///
public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete";
///
- /// ContentType alias for default relation type "Relate Parent Document On Delete".
+ /// Alias for default relation type "Relate Parent Document On Delete".
///
public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete";
///
- /// ContentType name for default relation type "Relate Parent Media Folder On Delete".
+ /// Name for default relation type "Relate Parent Media Folder On Delete".
///
public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete";
///
- /// ContentType alias for default relation type "Relate Parent Media Folder On Delete".
+ /// Alias for default relation type "Relate Parent Media Folder On Delete".
///
public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete";
+
+ ///
+ /// Returns the types of relations that are automatically tracked
+ ///
+ ///
+ /// Developers should not manually use these relation types since they will all be cleared whenever an entity
+ /// (content, media or member) is saved since they are auto-populated based on property values.
+ ///
+ public static string[] AutomaticRelationTypes = new[] { RelatedMediaAlias, RelatedDocumentAlias };
+
+ //TODO: return a list of built in types so we can use that to prevent deletion in the uI
}
}
}
diff --git a/src/Umbraco.Abstractions/Constants-UdiEntityType.cs b/src/Umbraco.Abstractions/Constants-UdiEntityType.cs
index 3bf6467060..e823eb7ff1 100644
--- a/src/Umbraco.Abstractions/Constants-UdiEntityType.cs
+++ b/src/Umbraco.Abstractions/Constants-UdiEntityType.cs
@@ -15,7 +15,7 @@
// need to keep it around in a field nor to make it readonly
- public const string Unknown = "unknown";
+ public const string Unknown = "unknown";
// guid entity types
diff --git a/src/Umbraco.Abstractions/Models/Editors/ContentPropertyFile.cs b/src/Umbraco.Abstractions/Models/Editors/ContentPropertyFile.cs
index ac236e1fdd..225e29a8a1 100644
--- a/src/Umbraco.Abstractions/Models/Editors/ContentPropertyFile.cs
+++ b/src/Umbraco.Abstractions/Models/Editors/ContentPropertyFile.cs
@@ -1,5 +1,6 @@
namespace Umbraco.Core.Models.Editors
{
+
///
/// Represents an uploaded file for a property.
///
diff --git a/src/Umbraco.Abstractions/Models/Entities/ITreeEntity.cs b/src/Umbraco.Abstractions/Models/Entities/ITreeEntity.cs
index afa3399202..ab63e1e1d8 100644
--- a/src/Umbraco.Abstractions/Models/Entities/ITreeEntity.cs
+++ b/src/Umbraco.Abstractions/Models/Entities/ITreeEntity.cs
@@ -24,7 +24,7 @@
/// Sets the parent entity.
///
/// Use this method to set the parent entity when the parent entity is known, but has not
- /// been persistent and does not yet have an identity. The parent identifier will we retrieved
+ /// been persistent and does not yet have an identity. The parent identifier will be retrieved
/// from the parent entity when needed. If the parent entity still does not have an entity by that
/// time, an exception will be thrown by getter.
void SetParent(ITreeEntity parent);
@@ -53,4 +53,4 @@
///
bool Trashed { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Abstractions/Models/Entities/MemberEntitySlim.cs b/src/Umbraco.Abstractions/Models/Entities/MemberEntitySlim.cs
index 335e269467..338f363856 100644
--- a/src/Umbraco.Abstractions/Models/Entities/MemberEntitySlim.cs
+++ b/src/Umbraco.Abstractions/Models/Entities/MemberEntitySlim.cs
@@ -1,13 +1,6 @@
namespace Umbraco.Core.Models.Entities
{
- public class MemberEntitySlim : EntitySlim, IMemberEntitySlim
+ public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim
{
- public string ContentTypeAlias { get; set; }
-
- ///
- public string ContentTypeIcon { get; set; }
-
- ///
- public string ContentTypeThumbnail { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Abstractions/Models/IDataValueEditor.cs b/src/Umbraco.Abstractions/Models/IDataValueEditor.cs
index e095f3aa31..0ac61b92ce 100644
--- a/src/Umbraco.Abstractions/Models/IDataValueEditor.cs
+++ b/src/Umbraco.Abstractions/Models/IDataValueEditor.cs
@@ -3,9 +3,11 @@ using System.ComponentModel.DataAnnotations;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Editors;
+using Umbraco.Core.Services;
namespace Umbraco.Core.PropertyEditors
{
+
///
/// Represents an editor for editing data values.
///
@@ -62,8 +64,26 @@ namespace Umbraco.Core.PropertyEditors
// TODO: / deal with this when unplugging the xml cache
// why property vs propertyType? services should be injected! etc...
- IEnumerable ConvertDbToXml(IProperty property, bool published);
- XNode ConvertDbToXml(IPropertyType propertyType, object value);
- string ConvertDbToString(IPropertyType propertyType, object value);
+
+ ///
+ /// Used for serializing an item for packaging
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable ConvertDbToXml(IProperty property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published);
+
+ ///
+ /// Used for serializing an item for packaging
+ ///
+ ///
+ ///
+ ///
+ ///
+ XNode ConvertDbToXml(IPropertyType propertyType, object value, IDataTypeService dataTypeService);
+
+ string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService);
}
}
diff --git a/src/Umbraco.Abstractions/Models/IRelation.cs b/src/Umbraco.Abstractions/Models/IRelation.cs
index 745216fba1..6bd348d72f 100644
--- a/src/Umbraco.Abstractions/Models/IRelation.cs
+++ b/src/Umbraco.Abstractions/Models/IRelation.cs
@@ -1,4 +1,5 @@
-using System.Runtime.Serialization;
+using System;
+using System.Runtime.Serialization;
using Umbraco.Core.Models.Entities;
namespace Umbraco.Core.Models
@@ -11,12 +12,18 @@ namespace Umbraco.Core.Models
[DataMember]
int ParentId { get; set; }
+ [DataMember]
+ Guid ParentObjectType { get; set; }
+
///
/// Gets or sets the Child Id of the Relation (Destination)
///
[DataMember]
int ChildId { get; set; }
+ [DataMember]
+ Guid ChildObjectType { get; set; }
+
///
/// Gets or sets the for the Relation
///
diff --git a/src/Umbraco.Abstractions/Models/IRelationType.cs b/src/Umbraco.Abstractions/Models/IRelationType.cs
index 8bbe657427..9253fae8ab 100644
--- a/src/Umbraco.Abstractions/Models/IRelationType.cs
+++ b/src/Umbraco.Abstractions/Models/IRelationType.cs
@@ -29,13 +29,13 @@ namespace Umbraco.Core.Models
///
/// Corresponds to the NodeObjectType in the umbracoNode table
[DataMember]
- Guid ParentObjectType { get; set; }
+ Guid? ParentObjectType { get; set; }
///
/// Gets or sets the Childs object type id
///
/// Corresponds to the NodeObjectType in the umbracoNode table
[DataMember]
- Guid ChildObjectType { get; set; }
+ Guid? ChildObjectType { get; set; }
}
}
diff --git a/src/Umbraco.Abstractions/Models/Relation.cs b/src/Umbraco.Abstractions/Models/Relation.cs
index f5d13c70c4..7afa476226 100644
--- a/src/Umbraco.Abstractions/Models/Relation.cs
+++ b/src/Umbraco.Abstractions/Models/Relation.cs
@@ -17,13 +17,36 @@ namespace Umbraco.Core.Models
private IRelationType _relationType;
private string _comment;
+ ///
+ /// Constructor for constructing the entity to be created
+ ///
+ ///
+ ///
+ ///
public Relation(int parentId, int childId, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
}
-
+
+ ///
+ /// Constructor for reconstructing the entity from the data source
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType)
+ {
+ _parentId = parentId;
+ _childId = childId;
+ _relationType = relationType;
+ ParentObjectType = parentObjectType;
+ ChildObjectType = childObjectType;
+ }
+
///
/// Gets or sets the Parent Id of the Relation (Source)
@@ -35,6 +58,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _parentId, nameof(ParentId));
}
+ [DataMember]
+ public Guid ParentObjectType { get; set; }
+
///
/// Gets or sets the Child Id of the Relation (Destination)
///
@@ -45,6 +71,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId));
}
+ [DataMember]
+ public Guid ChildObjectType { get; set; }
+
///
/// Gets or sets the for the Relation
///
diff --git a/src/Umbraco.Abstractions/Models/RelationType.cs b/src/Umbraco.Abstractions/Models/RelationType.cs
index 725628bf90..28290685c2 100644
--- a/src/Umbraco.Abstractions/Models/RelationType.cs
+++ b/src/Umbraco.Abstractions/Models/RelationType.cs
@@ -14,28 +14,24 @@ namespace Umbraco.Core.Models
private string _name;
private string _alias;
private bool _isBidrectional;
- private Guid _parentObjectType;
- private Guid _childObjectType;
+ private Guid? _parentObjectType;
+ private Guid? _childObjectType;
- public RelationType(Guid childObjectType, Guid parentObjectType, string alias)
+ public RelationType(string alias, string name)
+ : this(name, alias, false, null, null)
{
- if (alias == null) throw new ArgumentNullException(nameof(alias));
- if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
+ }
- _childObjectType = childObjectType;
- _parentObjectType = parentObjectType;
+ public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType)
+ {
+ _name = name;
_alias = alias;
- Name = _alias;
+ _isBidrectional = isBidrectional;
+ _parentObjectType = parentObjectType;
+ _childObjectType = childObjectType;
}
- public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name)
- : this(childObjectType, parentObjectType, alias)
- {
- if (name == null) throw new ArgumentNullException(nameof(name));
- if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
- Name = name;
- }
///
/// Gets or sets the Name of the RelationType
@@ -72,7 +68,7 @@ namespace Umbraco.Core.Models
///
/// Corresponds to the NodeObjectType in the umbracoNode table
[DataMember]
- public Guid ParentObjectType
+ public Guid? ParentObjectType
{
get => _parentObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType));
@@ -83,7 +79,7 @@ namespace Umbraco.Core.Models
///
/// Corresponds to the NodeObjectType in the umbracoNode table
[DataMember]
- public Guid ChildObjectType
+ public Guid? ChildObjectType
{
get => _childObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType));
diff --git a/src/Umbraco.Abstractions/Persistence/Repositories/IRelationRepository.cs b/src/Umbraco.Abstractions/Persistence/Repositories/IRelationRepository.cs
index 51d7656d8a..fc1be20e6f 100644
--- a/src/Umbraco.Abstractions/Persistence/Repositories/IRelationRepository.cs
+++ b/src/Umbraco.Abstractions/Persistence/Repositories/IRelationRepository.cs
@@ -1,9 +1,33 @@
-using Umbraco.Core.Models;
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IRelationRepository : IReadWriteQueryRepository
{
+ IEnumerable GetPagedRelationsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering);
+ ///
+ /// Persist multiple at once
+ ///
+ ///
+ void Save(IEnumerable relations);
+
+ ///
+ /// Deletes all relations for a parent for any specified relation type alias
+ ///
+ ///
+ ///
+ /// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted
+ ///
+ void DeleteByParent(int parentId, params string[] relationTypeAliases);
+
+ IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
+
+ IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
}
}
diff --git a/src/Umbraco.Abstractions/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Abstractions/PropertyEditors/PropertyEditorCollection.cs
index 86cfde2ee4..2149ece02a 100644
--- a/src/Umbraco.Abstractions/PropertyEditors/PropertyEditorCollection.cs
+++ b/src/Umbraco.Abstractions/PropertyEditors/PropertyEditorCollection.cs
@@ -4,6 +4,8 @@ using Umbraco.Core.Manifest;
namespace Umbraco.Core.PropertyEditors
{
+
+
public class PropertyEditorCollection : BuilderCollectionBase
{
public PropertyEditorCollection(DataEditorCollection dataEditors, IManifestParser manifestParser)
diff --git a/src/Umbraco.Abstractions/Services/IRelationService.cs b/src/Umbraco.Abstractions/Services/IRelationService.cs
index ef22632d6e..bf8bcd5b2a 100644
--- a/src/Umbraco.Abstractions/Services/IRelationService.cs
+++ b/src/Umbraco.Abstractions/Services/IRelationService.cs
@@ -8,136 +8,162 @@ namespace Umbraco.Core.Services
public interface IRelationService : IService
{
///
- /// Gets a by its Id
+ /// Gets a by its Id
///
- /// Id of the
- /// A object
+ /// Id of the
+ /// A object
IRelation GetById(int id);
///
- /// Gets a by its Id
+ /// Gets a by its Id
///
- /// Id of the
- /// A object
+ /// Id of the
+ /// A object
IRelationType GetRelationTypeById(int id);
///
- /// Gets a by its Id
+ /// Gets a by its Id
///
- /// Id of the
- /// A object
+ /// Id of the
+ /// A object
IRelationType GetRelationTypeById(Guid id);
///
- /// Gets a by its Alias
+ /// Gets a by its Alias
///
- /// Alias of the
- /// A object
+ /// Alias of the
+ /// A object
IRelationType GetRelationTypeByAlias(string alias);
///
- /// Gets all objects
+ /// Gets all objects
///
/// Optional array of integer ids to return relations for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetAllRelations(params int[] ids);
///
- /// Gets all objects by their
+ /// Gets all objects by their
///
- /// to retrieve Relations for
- /// An enumerable list of objects
- IEnumerable GetAllRelationsByRelationType(RelationType relationType);
+ /// to retrieve Relations for
+ /// An enumerable list of objects
+ IEnumerable GetAllRelationsByRelationType(IRelationType relationType);
///
- /// Gets all objects by their 's Id
+ /// Gets all objects by their 's Id
///
- /// Id of the to retrieve Relations for
- /// An enumerable list of objects
+ /// Id of the to retrieve Relations for
+ /// An enumerable list of objects
IEnumerable GetAllRelationsByRelationType(int relationTypeId);
///
- /// Gets all objects
+ /// Gets all objects
///
/// Optional array of integer ids to return relationtypes for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetAllRelationTypes(params int[] ids);
///
- /// Gets a list of objects by their parent Id
+ /// Gets a list of objects by their parent Id
///
/// Id of the parent to retrieve relations for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByParentId(int id);
///
- /// Gets a list of objects by their parent entity
+ /// Gets a list of objects by their parent Id
+ ///
+ /// Id of the parent to retrieve relations for
+ /// Alias of the type of relation to retrieve
+ /// An enumerable list of objects
+ IEnumerable GetByParentId(int id, string relationTypeAlias);
+
+ ///
+ /// Gets a list of objects by their parent entity
///
/// Parent Entity to retrieve relations for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByParent(IUmbracoEntity parent);
///
- /// Gets a list of objects by their parent entity
+ /// Gets a list of objects by their parent entity
///
/// Parent Entity to retrieve relations for
/// Alias of the type of relation to retrieve
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias);
///
- /// Gets a list of objects by their child Id
+ /// Gets a list of objects by their child Id
///
/// Id of the child to retrieve relations for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByChildId(int id);
///
- /// Gets a list of objects by their child Entity
+ /// Gets a list of objects by their child Id
+ ///
+ /// Id of the child to retrieve relations for
+ /// Alias of the type of relation to retrieve
+ /// An enumerable list of objects
+ IEnumerable GetByChildId(int id, string relationTypeAlias);
+
+ ///
+ /// Gets a list of objects by their child Entity
///
/// Child Entity to retrieve relations for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByChild(IUmbracoEntity child);
///
- /// Gets a list of objects by their child Entity
+ /// Gets a list of objects by their child Entity
///
/// Child Entity to retrieve relations for
/// Alias of the type of relation to retrieve
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias);
///
- /// Gets a list of objects by their child or parent Id.
+ /// Gets a list of objects by their child or parent Id.
/// Using this method will get you all relations regards of it being a child or parent relation.
///
/// Id of the child or parent to retrieve relations for
- /// An enumerable list of objects
+ /// An enumerable list of objects
IEnumerable GetByParentOrChildId(int id);
IEnumerable GetByParentOrChildId(int id, string relationTypeAlias);
///
- /// Gets a list of objects by the Name of the
+ /// Gets a list of objects by the Name of the
///
- /// Name of the to retrieve Relations for
- /// An enumerable list of objects
+ /// Name of the to retrieve Relations for
+ /// An enumerable list of objects
IEnumerable GetByRelationTypeName(string relationTypeName);
///
- /// Gets a list of objects by the Alias of the
+ /// Gets a list of objects by the Alias of the
///
- /// Alias of the to retrieve Relations for
- /// An enumerable list of objects
+ /// Alias of the to retrieve Relations for
+ /// An enumerable list of objects
IEnumerable GetByRelationTypeAlias(string relationTypeAlias);
///
- /// Gets a list of objects by the Id of the
+ /// Gets a list of objects by the Id of the
///
- /// Id of the to retrieve Relations for
- /// An enumerable list of objects
+ /// Id of the to retrieve Relations for
+ /// An enumerable list of objects
IEnumerable GetByRelationTypeId(int relationTypeId);
+ ///
+ /// Gets a paged result of
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null);
+
///
/// Gets the Child object from a Relation as an
///
@@ -173,6 +199,26 @@ namespace Umbraco.Core.Services
/// An enumerable list of
IEnumerable GetParentEntitiesFromRelations(IEnumerable relations);
+ ///
+ /// Returns paged parent entities for a related child id
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
+
+ ///
+ /// Returns paged child entities for a related parent id
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
+
///
/// Gets the Parent and Child objects from a list of Relations as a list of objects.
///
@@ -186,7 +232,7 @@ namespace Umbraco.Core.Services
/// Id of the parent
/// Id of the child
/// The type of relation to create
- /// The created
+ /// The created
IRelation Relate(int parentId, int childId, IRelationType relationType);
///
@@ -195,7 +241,7 @@ namespace Umbraco.Core.Services
/// Parent entity
/// Child entity
/// The type of relation to create
- /// The created
+ /// The created
IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType);
///
@@ -204,7 +250,7 @@ namespace Umbraco.Core.Services
/// Id of the parent
/// Id of the child
/// Alias of the type of relation to create
- /// The created
+ /// The created
IRelation Relate(int parentId, int childId, string relationTypeAlias);
///
@@ -213,14 +259,14 @@ namespace Umbraco.Core.Services
/// Parent entity
/// Child entity
/// Alias of the type of relation to create
- /// The created
+ /// The created
IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
///
- /// Checks whether any relations exists for the passed in .
+ /// Checks whether any relations exists for the passed in .
///
- /// to check for relations
- /// Returns True if any relations exists for the given , otherwise False
+ /// to check for relations
+ /// Returns True if any relations exists for the given , otherwise False
bool HasRelations(IRelationType relationType);
///
@@ -265,33 +311,35 @@ namespace Umbraco.Core.Services
bool AreRelated(int parentId, int childId, string relationTypeAlias);
///
- /// Saves a
+ /// Saves a
///
/// Relation to save
void Save(IRelation relation);
+ void Save(IEnumerable relations);
+
///
- /// Saves a
+ /// Saves a
///
/// RelationType to Save
void Save(IRelationType relationType);
///
- /// Deletes a
+ /// Deletes a
///
/// Relation to Delete
void Delete(IRelation relation);
///
- /// Deletes a
+ /// Deletes a
///
/// RelationType to Delete
void Delete(IRelationType relationType);
///
- /// Deletes all objects based on the passed in
+ /// Deletes all objects based on the passed in
///
- /// to Delete Relations for
+ /// to Delete Relations for
void DeleteRelationsOfType(IRelationType relationType);
}
}
diff --git a/src/Umbraco.Abstractions/UnknownTypeUdi.cs b/src/Umbraco.Abstractions/UnknownTypeUdi.cs
index ac42205cbc..c6ad48bb79 100644
--- a/src/Umbraco.Abstractions/UnknownTypeUdi.cs
+++ b/src/Umbraco.Abstractions/UnknownTypeUdi.cs
@@ -1,6 +1,6 @@
namespace Umbraco.Core
{
- internal class UnknownTypeUdi : Udi
+ public class UnknownTypeUdi : Udi
{
private UnknownTypeUdi()
: base("unknown", "umb://unknown/")
diff --git a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs
index 63a7e170da..b56ff8b87e 100644
--- a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs
+++ b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs
@@ -26,10 +26,11 @@ namespace Umbraco.Core.Compose
if (relationType == null)
{
- relationType = new RelationType(Constants.ObjectTypes.Document,
+ relationType = new RelationType(Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias,
+ Constants.Conventions.RelationTypes.RelateDocumentOnCopyName,
+ true,
Constants.ObjectTypes.Document,
- Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias,
- Constants.Conventions.RelationTypes.RelateDocumentOnCopyName) { IsBidirectional = true };
+ Constants.ObjectTypes.Document);
relationService.Save(relationType);
}
diff --git a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs
index 8371f9b279..4e01c50fc6 100644
--- a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs
+++ b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs
@@ -63,7 +63,7 @@ namespace Umbraco.Core.Compose
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName;
- relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
+ relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
relationService.Save(relationType);
}
@@ -106,7 +106,7 @@ namespace Umbraco.Core.Compose
{
var documentObjectType = Constants.ObjectTypes.Document;
const string relationTypeName = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName;
- relationType = new RelationType(documentObjectType, documentObjectType, relationTypeAlias, relationTypeName);
+ relationType = new RelationType(relationTypeName, relationTypeAlias, false, documentObjectType, documentObjectType);
relationService.Save(relationType);
}
foreach (var item in e.MoveInfoCollection)
diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs
index 4eba166020..77a3ef5049 100644
--- a/src/Umbraco.Core/Composing/Current.cs
+++ b/src/Umbraco.Core/Composing/Current.cs
@@ -157,6 +157,9 @@ namespace Umbraco.Core.Composing
public static DataEditorCollection DataEditors
=> Factory.GetInstance();
+ public static DataValueReferenceFactoryCollection DataValueReferenceFactories
+ => Factory.GetInstance();
+
public static PropertyEditorCollection PropertyEditors
=> Factory.GetInstance();
diff --git a/src/Umbraco.Core/CompositionExtensions.cs b/src/Umbraco.Core/CompositionExtensions.cs
index 1fd1de0c08..bd221e6fd3 100644
--- a/src/Umbraco.Core/CompositionExtensions.cs
+++ b/src/Umbraco.Core/CompositionExtensions.cs
@@ -49,6 +49,13 @@ namespace Umbraco.Core
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
=> composition.WithCollectionBuilder();
+ ///
+ /// Gets the data value reference factory collection builder.
+ ///
+ /// The composition.
+ public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this Composition composition)
+ => composition.WithCollectionBuilder();
+
///
/// Gets the property value converters collection builder.
///
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
index 008331f153..41aceb8a69 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
@@ -309,14 +309,27 @@ namespace Umbraco.Core.Migrations.Install
private void CreateRelationTypeData()
{
var relationType = new RelationTypeDto { Id = 1, Alias = Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = true, Name = Constants.Conventions.RelationTypes.RelateDocumentOnCopyName };
- relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
+ relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 2, Alias = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Document, ParentObjectType = Constants.ObjectTypes.Document, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteName };
- relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
+ relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
relationType = new RelationTypeDto { Id = 3, Alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias, ChildObjectType = Constants.ObjectTypes.Media, ParentObjectType = Constants.ObjectTypes.Media, Dual = false, Name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName };
- relationType.UniqueId = (relationType.Alias + "____" + relationType.Name).ToGuid();
+ relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
_database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
+
+ relationType = new RelationTypeDto { Id = 4, Alias = Constants.Conventions.RelationTypes.RelatedMediaAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedMediaName };
+ relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
+ _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
+
+ relationType = new RelationTypeDto { Id = 5, Alias = Constants.Conventions.RelationTypes.RelatedDocumentAlias, ChildObjectType = null, ParentObjectType = null, Dual = false, Name = Constants.Conventions.RelationTypes.RelatedDocumentName };
+ relationType.UniqueId = CreateUniqueRelationTypeId(relationType.Alias, relationType.Name);
+ _database.Insert(Constants.DatabaseSchema.Tables.RelationType, "id", false, relationType);
+ }
+
+ internal static Guid CreateUniqueRelationTypeId(string alias, string name)
+ {
+ return (alias + "____" + name).ToGuid();
}
private void CreateKeyValueData()
diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
index ed440eb126..7e27a857c5 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -184,6 +184,10 @@ namespace Umbraco.Core.Migrations.Upgrade
To("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
+ // to 8.5.0...
+ To("{4759A294-9860-46BC-99F9-B4C975CAE580}");
+ To("{0BC866BC-0665-487A-9913-0290BD0169AD}");
+
// to 8.6.0
To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs
new file mode 100644
index 0000000000..2e2e00a9bc
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/AddNewRelationTypes.cs
@@ -0,0 +1,33 @@
+using Umbraco.Core.Migrations.Install;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
+{
+ ///
+ /// Ensures the new relation types are created
+ ///
+ public class AddNewRelationTypes : MigrationBase
+ {
+ public AddNewRelationTypes(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+ CreateRelation(
+ Constants.Conventions.RelationTypes.RelatedMediaAlias,
+ Constants.Conventions.RelationTypes.RelatedMediaName);
+
+ CreateRelation(
+ Constants.Conventions.RelationTypes.RelatedDocumentAlias,
+ Constants.Conventions.RelationTypes.RelatedDocumentName);
+ }
+
+ private void CreateRelation(string alias, string name)
+ {
+ var uniqueId = DatabaseDataCreator.CreateUniqueRelationTypeId(alias ,name); //this is the same as how it installs so everything is consistent
+ Insert.IntoTable(Constants.DatabaseSchema.Tables.RelationType)
+ .Row(new { typeUniqueId = uniqueId, dual = 0, name, alias })
+ .Do();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs
new file mode 100644
index 0000000000..c79f43d20f
--- /dev/null
+++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_6_0/UpdateRelationTypeTable.cs
@@ -0,0 +1,38 @@
+using Umbraco.Core.Persistence.Dtos;
+
+namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
+{
+
+ public class UpdateRelationTypeTable : MigrationBase
+ {
+ public UpdateRelationTypeTable(IMigrationContext context)
+ : base(context)
+ { }
+
+ public override void Migrate()
+ {
+
+ Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("parentObjectType").AsGuid().Nullable().Do();
+ Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("childObjectType").AsGuid().Nullable().Do();
+
+ //TODO: We have to update this field to ensure it's not null, we can just copy across the name since that is not nullable
+
+ //drop index before we can alter the column
+ if (IndexExists("IX_umbracoRelationType_alias"))
+ Delete
+ .Index("IX_umbracoRelationType_alias")
+ .OnTable(Constants.DatabaseSchema.Tables.RelationType)
+ .Do();
+ //change the column to non nullable
+ Alter.Table(Constants.DatabaseSchema.Tables.RelationType).AlterColumn("alias").AsString(100).NotNullable().Do();
+ //re-create the index
+ Create
+ .Index("IX_umbracoRelationType_alias")
+ .OnTable(Constants.DatabaseSchema.Tables.RelationType)
+ .OnColumn("alias")
+ .Ascending()
+ .WithOptions().Unique().WithOptions().NonClustered()
+ .Do();
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
new file mode 100644
index 0000000000..fa7fb398f0
--- /dev/null
+++ b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Models.Editors
+{
+ ///
+ /// Used to track reference to other entities in a property value
+ ///
+ public struct UmbracoEntityReference : IEquatable
+ {
+ private static readonly UmbracoEntityReference _empty = new UmbracoEntityReference(UnknownTypeUdi.Instance, string.Empty);
+
+ public UmbracoEntityReference(Udi udi, string relationTypeAlias)
+ {
+ Udi = udi ?? throw new ArgumentNullException(nameof(udi));
+ RelationTypeAlias = relationTypeAlias ?? throw new ArgumentNullException(nameof(relationTypeAlias));
+ }
+
+ public UmbracoEntityReference(Udi udi)
+ {
+ Udi = udi ?? throw new ArgumentNullException(nameof(udi));
+
+ switch (udi.EntityType)
+ {
+ case Constants.UdiEntityType.Media:
+ RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedMediaAlias;
+ break;
+ default:
+ RelationTypeAlias = Constants.Conventions.RelationTypes.RelatedDocumentAlias;
+ break;
+ }
+ }
+
+ public static UmbracoEntityReference Empty() => _empty;
+
+ public static bool IsEmpty(UmbracoEntityReference reference) => reference == Empty();
+
+ public Udi Udi { get; }
+ public string RelationTypeAlias { get; }
+
+ public override bool Equals(object obj)
+ {
+ return obj is UmbracoEntityReference reference && Equals(reference);
+ }
+
+ public bool Equals(UmbracoEntityReference other)
+ {
+ return EqualityComparer.Default.Equals(Udi, other.Udi) &&
+ RelationTypeAlias == other.RelationTypeAlias;
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = -487348478;
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Udi);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(RelationTypeAlias);
+ return hashCode;
+ }
+
+ public static bool operator ==(UmbracoEntityReference left, UmbracoEntityReference right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(UmbracoEntityReference left, UmbracoEntityReference right)
+ {
+ return !(left == right);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Models/ObjectTypes.cs b/src/Umbraco.Core/Models/ObjectTypes.cs
index dd943ee02b..2ddbdca77a 100644
--- a/src/Umbraco.Core/Models/ObjectTypes.cs
+++ b/src/Umbraco.Core/Models/ObjectTypes.cs
@@ -41,7 +41,7 @@ namespace Umbraco.Core.Models
///
public static UmbracoObjectTypes GetUmbracoObjectType(string name)
{
- return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, false);
+ return (UmbracoObjectTypes) Enum.Parse(typeof (UmbracoObjectTypes), name, true);
}
#region Guid object type utilities
diff --git a/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs
index f1fd3007d7..b21866eb8b 100644
--- a/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/RelationDto.cs
@@ -34,5 +34,13 @@ namespace Umbraco.Core.Persistence.Dtos
[Column("comment")]
[Length(1000)]
public string Comment { get; set; }
+
+ [ResultColumn]
+ [Column("parentObjectType")]
+ public Guid ParentObjectType { get; set; }
+
+ [ResultColumn]
+ [Column("childObjectType")]
+ public Guid ChildObjectType { get; set; }
}
}
diff --git a/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs
index e972192844..d3e107d23f 100644
--- a/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs
+++ b/src/Umbraco.Core/Persistence/Dtos/RelationTypeDto.cs
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Dtos
[ExplicitColumns]
internal class RelationTypeDto
{
- public const int NodeIdSeed = 4;
+ public const int NodeIdSeed = 10;
[Column("id")]
[PrimaryKeyColumn(IdentitySeed = NodeIdSeed)]
@@ -23,17 +23,20 @@ namespace Umbraco.Core.Persistence.Dtos
public bool Dual { get; set; }
[Column("parentObjectType")]
- public Guid ParentObjectType { get; set; }
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public Guid? ParentObjectType { get; set; }
[Column("childObjectType")]
- public Guid ChildObjectType { get; set; }
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public Guid? ChildObjectType { get; set; }
[Column("name")]
+ [NullSetting(NullSetting = NullSettings.NotNull)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")]
public string Name { get; set; }
[Column("alias")]
- [NullSetting(NullSetting = NullSettings.Null)]
+ [NullSetting(NullSetting = NullSettings.NotNull)]
[Length(100)]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")]
public string Alias { get; set; }
diff --git a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs
index 6abb858e94..d8f100cdbe 100644
--- a/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/RelationFactory.cs
@@ -3,20 +3,11 @@ using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Persistence.Factories
{
- internal class RelationFactory
+ internal static class RelationFactory
{
- private readonly IRelationType _relationType;
-
- public RelationFactory(IRelationType relationType)
+ public static IRelation BuildEntity(RelationDto dto, IRelationType relationType)
{
- _relationType = relationType;
- }
-
- #region Implementation of IEntityFactory
-
- public IRelation BuildEntity(RelationDto dto)
- {
- var entity = new Relation(dto.ParentId, dto.ChildId, _relationType);
+ var entity = new Relation(dto.ParentId, dto.ChildId, dto.ParentObjectType, dto.ChildObjectType, relationType);
try
{
@@ -37,7 +28,7 @@ namespace Umbraco.Core.Persistence.Factories
}
}
- public RelationDto BuildDto(IRelation entity)
+ public static RelationDto BuildDto(IRelation entity)
{
var dto = new RelationDto
{
@@ -54,6 +45,5 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
- #endregion
}
}
diff --git a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs
index ca6928a0a1..edd87fec68 100644
--- a/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/RelationTypeFactory.cs
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IRelationType BuildEntity(RelationTypeDto dto)
{
- var entity = new RelationType(dto.ChildObjectType, dto.ParentObjectType, dto.Alias);
+ var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ChildObjectType, dto.ParentObjectType);
try
{
@@ -17,8 +17,6 @@ namespace Umbraco.Core.Persistence.Factories
entity.Id = dto.Id;
entity.Key = dto.UniqueId;
- entity.IsBidirectional = dto.Dual;
- entity.Name = dto.Name;
// reset dirty initial properties (U4-1946)
entity.ResetDirtyProperties(false);
diff --git a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
index 0574e37c4c..10db1ca18e 100644
--- a/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
+++ b/src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs
@@ -14,7 +14,21 @@ namespace Umbraco.Core.Persistence
///
public static partial class NPocoDatabaseExtensions
{
- // TODO: review NPoco native InsertBulk to replace the code below
+ ///
+ /// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction
+ ///
+ ///
+ /// This is required to use NPoco's own method because we use wrapped DbConnection and DbTransaction instances.
+ /// NPoco's InsertBulk method only caters for efficient bulk inserting records for Sql Server, it does not cater for bulk inserting of records for
+ /// any other database type and in which case will just insert records one at a time.
+ /// NPoco's InsertBulk method also deals with updating the passed in entity's PK/ID once it's inserted whereas our own BulkInsertRecords methods
+ /// do not handle this scenario.
+ ///
+ public static void ConfigureNPocoBulkExtensions()
+ {
+ SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection(dbConn);
+ SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction(dbTran);
+ }
///
/// Bulk-inserts records within a transaction.
@@ -235,7 +249,7 @@ namespace Umbraco.Core.Persistence
//we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared
//to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses
//the names instead of their ordering.
- foreach(var col in bulkReader.ColumnMappings)
+ foreach (var col in bulkReader.ColumnMappings)
{
copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn);
}
diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
index 80042dec23..ff3590439a 100644
--- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
+++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs
@@ -14,10 +14,6 @@ namespace Umbraco.Core.Persistence
public static partial class NPocoSqlExtensions
{
-#region Special extensions
-
- #endregion
-
#region Where
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs
index 69f6ef4c5f..a0ddcac8e6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs
@@ -1,4 +1,5 @@
-using System;
+using NPoco;
+using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -15,10 +16,22 @@ namespace Umbraco.Core.Persistence.Repositories
IEntitySlim Get(int id, Guid objectTypeId);
IEntitySlim Get(Guid key, Guid objectTypeId);
- IEnumerable GetAll(Guid objectType, params int[] ids);
+ IEnumerable GetAll(Guid objectType, params int[] ids);
IEnumerable GetAll(Guid objectType, params Guid[] keys);
+ ///
+ /// Gets entities for a query
+ ///
+ ///
+ ///
IEnumerable GetByQuery(IQuery query);
+
+ ///
+ /// Gets entities for a query and a specific object type allowing the query to be slightly more optimized
+ ///
+ ///
+ ///
+ ///
IEnumerable GetByQuery(IQuery query, Guid objectType);
UmbracoObjectTypes GetObjectType(int id);
@@ -30,7 +43,41 @@ namespace Umbraco.Core.Persistence.Repositories
bool Exists(int id);
bool Exists(Guid key);
+ ///
+ /// Gets paged entities for a query and a subset of object types
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// A callback providing the ability to customize the generated SQL used to retrieve entities
+ ///
+ ///
+ /// A collection of mixed entity types which would be of type , , ,
+ ///
+ ///
+ IEnumerable GetPagedResultsByQuery(
+ IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
+ IQuery filter, Ordering ordering, Action> sqlCustomization = null);
+
+ ///
+ /// Gets paged entities for a query and a specific object type
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery filter, Ordering ordering);
+
+
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 5803bc7b1d..58b19d2ad6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
+using Umbraco.Core.Models.Editors;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -23,26 +24,48 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal sealed class ContentRepositoryBase
{
///
+ ///
/// This is used for unit tests ONLY
///
public static bool ThrowOnWarning = false;
}
internal abstract class ContentRepositoryBase : NPocoRepositoryBase, IContentRepository
- where TEntity : class, IUmbracoEntity
+ where TEntity : class, IContentBase
where TRepository : class, IRepository
{
- protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILanguageRepository languageRepository, ILogger logger)
+ private readonly Lazy _propertyEditors;
+ private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
+ ///
+ protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
+ ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
+ Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, cache, logger)
{
LanguageRepository = languageRepository;
+ RelationRepository = relationRepository;
+ RelationTypeRepository = relationTypeRepository;
+ _propertyEditors = propertyEditors;
+ _dataValueReferenceFactories = dataValueReferenceFactories;
}
protected abstract TRepository This { get; }
protected ILanguageRepository LanguageRepository { get; }
+ protected IRelationRepository RelationRepository { get; }
+ protected IRelationTypeRepository RelationTypeRepository { get; }
- protected PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // TODO: inject
+ protected PropertyEditorCollection PropertyEditors => _propertyEditors.Value;
#region Versions
@@ -796,5 +819,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
#endregion
+
+ protected void PersistRelations(TEntity entity)
+ {
+ // Get all references from our core built in DataEditors/Property Editors
+ // Along with seeing if deverlopers want to collect additional references from the DataValueReferenceFactories collection
+ var trackedRelations = new List();
+ trackedRelations.AddRange(_dataValueReferenceFactories.GetAllReferences(entity.Properties, PropertyEditors));
+
+ //First delete all auto-relations for this entity
+ RelationRepository.DeleteByParent(entity.Id, Constants.Conventions.RelationTypes.AutomaticRelationTypes);
+
+ if (trackedRelations.Count == 0) return;
+
+ trackedRelations = trackedRelations.Distinct().ToList();
+ var udiToGuids = trackedRelations.Select(x => x.Udi as GuidUdi)
+ .ToDictionary(x => (Udi)x, x => x.Guid);
+
+ //lookup in the DB all INT ids for the GUIDs and chuck into a dictionary
+ var keyToIds = Database.Fetch(Sql().Select(x => x.NodeId, x => x.UniqueId).From().WhereIn(x => x.UniqueId, udiToGuids.Values))
+ .ToDictionary(x => x.UniqueId, x => x.NodeId);
+
+ var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty())
+ .ToDictionary(x => x.Alias, x => x);
+
+ var toSave = trackedRelations.Select(rel =>
+ {
+ if (!allRelationTypes.TryGetValue(rel.RelationTypeAlias, out var relationType))
+ throw new InvalidOperationException($"The relation type {rel.RelationTypeAlias} does not exist");
+
+ if (!udiToGuids.TryGetValue(rel.Udi, out var guid))
+ return null; // This shouldn't happen!
+
+ if (!keyToIds.TryGetValue(guid, out var id))
+ return null; // This shouldn't happen!
+
+ return new Relation(entity.Id, id, relationType);
+ }).WhereNotNull();
+
+ // Save bulk relations
+ RelationRepository.Save(toSave);
+
+ }
+
+ private class NodeIdKey
+ {
+ [Column("id")]
+ public int NodeId { get; set; }
+
+ [Column("uniqueId")]
+ public Guid UniqueId { get; set; }
+ }
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs
index d137d7ac76..e150b2e632 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentBlueprintRepository.cs
@@ -2,6 +2,7 @@
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
+using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
@@ -17,8 +18,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
internal class DocumentBlueprintRepository : DocumentRepository, IDocumentBlueprintRepository
{
- public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
- : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository)
+ public DocumentBlueprintRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
+ IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
+ Lazy propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
+ : base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
index 1d28f3530d..80265cdc83 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -12,6 +12,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
@@ -30,8 +31,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ContentByGuidReadRepository _contentByGuidReadRepository;
private readonly IScopeAccessor _scopeAccessor;
- public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
- : base(scopeAccessor, appCaches, languageRepository, logger)
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors
+ ///
+ public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
+ IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
+ Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
+ : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories)
{
_contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository));
_templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository));
@@ -468,6 +484,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
+ PersistRelations(entity);
+
entity.ResetDirtyProperties();
// troubleshooting
@@ -671,6 +689,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
+ PersistRelations(entity);
+
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
entity.ResetDirtyProperties();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index 09c427e230..c3f9f03ec0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -34,21 +34,33 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
#region Repository
-
- // get a page of entities
+
public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery filter, Ordering ordering)
{
- var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
- var isMedia = objectType == Constants.ObjectTypes.Media;
- var isMember = objectType == Constants.ObjectTypes.Member;
+ return GetPagedResultsByQuery(query, new[] { objectType }, pageIndex, pageSize, out totalRecords, filter, ordering);
+ }
- var sql = GetBaseWhere(isContent, isMedia, isMember, false, x =>
+ // get a page of entities
+ public IEnumerable GetPagedResultsByQuery(IQuery query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
+ IQuery filter, Ordering ordering, Action> sqlCustomization = null)
+ {
+ var isContent = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint);
+ var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media);
+ var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member);
+
+ var sql = GetBaseWhere(isContent, isMedia, isMember, false, s =>
{
- if (filter == null) return;
- foreach (var filterClause in filter.GetWhereClauses())
- x.Where(filterClause.Item1, filterClause.Item2);
- }, objectType);
+ sqlCustomization?.Invoke(s);
+
+ if (filter != null)
+ {
+ foreach (var filterClause in filter.GetWhereClauses())
+ s.Where(filterClause.Item1, filterClause.Item2);
+ }
+
+
+ }, objectTypes);
ordering = ordering ?? Ordering.ByDefault();
@@ -69,35 +81,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
var pageIndexToFetch = pageIndex + 1;
IEnumerable dtos;
- if(isContent)
- {
- var page = Database.Page(pageIndexToFetch, pageSize, sql);
- dtos = page.Items;
- totalRecords = page.TotalItems;
- }
- else if (isMedia)
- {
- var page = Database.Page(pageIndexToFetch, pageSize, sql);
- dtos = page.Items;
- totalRecords = page.TotalItems;
- }
- else if (isMember)
- {
- var page = Database.Page(pageIndexToFetch, pageSize, sql);
- dtos = page.Items;
- totalRecords = page.TotalItems;
- }
- else
- {
- var page = Database.Page(pageIndexToFetch, pageSize, sql);
- dtos = page.Items;
- totalRecords = page.TotalItems;
- }
+ var page = Database.Page(pageIndexToFetch, pageSize, sql);
+ dtos = page.Items;
+ totalRecords = page.TotalItems;
- var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray();
+ var entities = dtos.Select(BuildEntity).ToArray();
- if (isContent)
- BuildVariants(entities.Cast());
+ BuildVariants(entities.OfType());
return entities;
}
@@ -106,7 +96,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, key);
var dto = Database.FirstOrDefault(sql);
- return dto == null ? null : BuildEntity(false, false, false, dto);
+ return dto == null ? null : BuildEntity(dto);
}
@@ -115,7 +105,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
- var cdtos = Database.Fetch(sql);
+ var cdtos = Database.Fetch(sql);
return cdtos.Count == 0 ? null : BuildVariants(BuildDocumentEntity(cdtos[0]));
}
@@ -126,7 +116,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (dto == null) return null;
- var entity = BuildEntity(false, isMedia, isMember, dto);
+ var entity = BuildEntity(dto);
return entity;
}
@@ -145,7 +135,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, id);
var dto = Database.FirstOrDefault(sql);
- return dto == null ? null : BuildEntity(false, false, false, dto);
+ return dto == null ? null : BuildEntity(dto);
}
public IEntitySlim Get(int id, Guid objectTypeId)
@@ -177,7 +167,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
//isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
- var cdtos = Database.Fetch(sql);
+ var cdtos = Database.Fetch(sql);
return cdtos.Count == 0
? Enumerable.Empty()
@@ -188,7 +178,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
? (IEnumerable)Database.Fetch(sql)
: Database.Fetch(sql);
- var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray();
+ var entities = dtos.Select(BuildEntity).ToArray();
return entities;
}
@@ -232,7 +222,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var sql = translator.Translate();
sql = AddGroupBy(false, false, false, sql, true);
var dtos = Database.Fetch(sql);
- return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
+ return dtos.Select(BuildEntity).ToList();
}
public IEnumerable GetByQuery(IQuery query, Guid objectType)
@@ -241,7 +231,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
- var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType);
+ var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, new[] { objectType });
var translator = new SqlTranslator(sql, query);
sql = translator.Translate();
@@ -355,14 +345,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the full sql for a given object type, with a given filter
protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action> filter)
{
- var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType);
+ var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, new[] { objectType });
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version
protected Sql GetBase(bool isContent, bool isMedia, bool isMember, Action> filter, bool isCount = false)
- {
+ {
var sql = Sql();
if (isCount)
@@ -400,15 +390,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isContent || isMedia || isMember)
{
sql
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current)
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
- .InnerJoin().On((left, right) => left.ContentTypeId == right.NodeId);
+ .LeftJoin().On((left, right) => left.NodeId == right.NodeId && right.Current)
+ .LeftJoin().On((left, right) => left.NodeId == right.NodeId)
+ .LeftJoin().On((left, right) => left.ContentTypeId == right.NodeId);
}
if (isContent)
{
sql
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId);
+ .LeftJoin().On((left, right) => left.NodeId == right.NodeId);
}
if (isMedia)
@@ -432,10 +422,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the base SELECT + FROM [+ filter] + WHERE sql
// for a given object type, with a given filter
- protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid objectType)
+ protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action> filter, Guid[] objectTypes)
{
- return GetBase(isContent, isMedia, isMember, filter, isCount)
- .Where(x => x.NodeObjectType == objectType);
+ var sql = GetBase(isContent, isMedia, isMember, filter, isCount);
+ if (objectTypes.Length > 0)
+ {
+ sql.WhereIn(x => x.NodeObjectType, objectTypes);
+ }
+ return sql;
}
// gets the base SELECT + FROM + WHERE sql
@@ -509,8 +503,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (sql == null) throw new ArgumentNullException(nameof(sql));
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
- // TODO: although this works for name, it probably doesn't work for others without an alias of some sort
- var orderBy = ordering.OrderBy;
+ // TODO: although the default ordering string works for name, it wont work for others without a table or an alias of some sort
+ // As more things are attempted to be sorted we'll prob have to add more expressions here
+ string orderBy;
+ switch (ordering.OrderBy.ToUpperInvariant())
+ {
+ case "PATH":
+ orderBy = SqlSyntax.GetQuotedColumn(NodeDto.TableName, "path");
+ break;
+
+ default:
+ orderBy = ordering.OrderBy;
+ break;
+ }
if (ordering.Direction == Direction.Ascending)
sql.OrderBy(orderBy);
@@ -523,9 +528,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Classes
///
- /// The DTO used to fetch results for a content item with its variation info
+ /// The DTO used to fetch results for a generic content item which could be either a document, media or a member
///
- private class ContentEntityDto : BaseDto
+ private class GenericContentEntityDto : DocumentEntityDto
+ {
+ public string MediaPath { get; set; }
+ }
+
+ ///
+ /// The DTO used to fetch results for a document item with its variation info
+ ///
+ private class DocumentEntityDto : BaseDto
{
public ContentVariation Variations { get; set; }
@@ -533,11 +546,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public bool Edited { get; set; }
}
+ ///
+ /// The DTO used to fetch results for a media item with its media path info
+ ///
private class MediaEntityDto : BaseDto
{
public string MediaPath { get; set; }
}
+ ///
+ /// The DTO used to fetch results for a member item
+ ///
private class MemberEntityDto : BaseDto
{
}
@@ -588,13 +607,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Factory
- private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto)
+ private EntitySlim BuildEntity(BaseDto dto)
{
- if (isContent)
+ if (dto.NodeObjectType == Constants.ObjectTypes.Document)
return BuildDocumentEntity(dto);
- if (isMedia)
+ if (dto.NodeObjectType == Constants.ObjectTypes.Media)
return BuildMediaEntity(dto);
- if (isMember)
+ if (dto.NodeObjectType == Constants.ObjectTypes.Member)
return BuildMemberEntity(dto);
// EntitySlim does not track changes
@@ -649,7 +668,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var entity = new DocumentEntitySlim();
BuildContentEntity(entity, dto);
- if (dto is ContentEntityDto contentDto)
+ if (dto is DocumentEntityDto contentDto)
{
// fill in the invariant info
entity.Edited = contentDto.Edited;
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
index ca78198fc3..7db9e57123 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
@@ -26,8 +27,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly MediaByGuidReadRepository _mediaByGuidReadRepository;
- public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
- : base(scopeAccessor, cache, languageRepository, logger)
+ public MediaRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
+ Lazy propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
+ : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
_mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
@@ -286,6 +288,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// set tags
SetEntityTags(entity, _tagRepository);
+ PersistRelations(entity);
+
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -342,6 +346,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
+ PersistRelations(entity);
+
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
index 7ddc55b6db..1a8bea174b 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs
@@ -10,6 +10,7 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
@@ -25,8 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private readonly ITagRepository _tagRepository;
private readonly IMemberGroupRepository _memberGroupRepository;
- public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository)
- : base(scopeAccessor, cache, languageRepository, logger)
+ public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
+ IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
+ Lazy propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories)
+ : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferenceFactories)
{
_memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository));
_tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository));
@@ -317,6 +320,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
+ PersistRelations(entity);
+
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -382,6 +387,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
+ PersistRelations(entity);
+
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
index 4b4af505b8..667e997953 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
@@ -9,7 +9,10 @@ using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Scoping;
+using Umbraco.Core.Services;
+using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -19,11 +22,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal class RelationRepository : NPocoRepositoryBase, IRelationRepository
{
private readonly IRelationTypeRepository _relationTypeRepository;
+ private readonly IEntityRepository _entityRepository;
- public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository)
+ public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepository entityRepository)
: base(scopeAccessor, AppCaches.NoCache, logger)
{
_relationTypeRepository = relationTypeRepository;
+ _entityRepository = entityRepository;
}
#region Overrides of RepositoryBase
@@ -39,10 +44,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var relationType = _relationTypeRepository.Get(dto.RelationType);
if (relationType == null)
- throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
+ throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType));
- var factory = new RelationFactory(relationType);
- return DtoToEntity(dto, factory);
+ return DtoToEntity(dto, relationType);
}
protected override IEnumerable PerformGetAll(params int[] ids)
@@ -67,26 +71,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable DtosToEntities(IEnumerable dtos)
{
- // in most cases, the relation type will be the same for all of them,
- // plus we've ordered the relations by type, so try to allocate as few
- // factories as possible - bearing in mind that relation types are cached
- RelationFactory factory = null;
- var relationTypeId = -1;
+ //NOTE: This is N+1, BUT ALL relation types are cached so shouldn't matter
- return dtos.Select(x =>
- {
- if (relationTypeId != x.RelationType)
- factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType));
- return DtoToEntity(x, factory);
- }).ToList();
+ return dtos.Select(x => DtoToEntity(x, _relationTypeRepository.Get(x.RelationType))).ToList();
}
- private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory)
+ private static IRelation DtoToEntity(RelationDto dto, IRelationType relationType)
{
- var entity = factory.BuildEntity(dto);
+ var entity = RelationFactory.BuildEntity(dto, relationType);
// reset dirty initial properties (U4-1946)
- ((BeingDirtyBase)entity).ResetDirtyProperties(false);
+ entity.ResetDirtyProperties(false);
return entity;
}
@@ -97,14 +92,18 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override Sql GetBaseQuery(bool isCount)
{
- var sql = Sql();
+ if (isCount)
+ {
+ return Sql().SelectCount().From();
+ }
- sql = isCount
- ? sql.SelectCount()
- : sql.Select();
+ var sql = Sql().Select()
+ .AndSelect("uchild", x => Alias(x.NodeObjectType, "childObjectType"))
+ .AndSelect("uparent", x => Alias(x.NodeObjectType, "parentObjectType"))
+ .From()
+ .InnerJoin("uchild").On((rel, node) => rel.ChildId == node.NodeId, aliasRight: "uchild")
+ .InnerJoin("uparent").On((rel, node) => rel.ParentId == node.NodeId, aliasRight: "uparent");
- sql
- .From();
return sql;
}
@@ -136,11 +135,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.AddingEntity();
- var factory = new RelationFactory(entity.RelationType);
- var dto = factory.BuildDto(entity);
+ var dto = RelationFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
+
entity.Id = id;
+ PopulateObjectTypes(entity);
entity.ResetDirtyProperties();
}
@@ -149,13 +149,192 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entity.UpdatingEntity();
- var factory = new RelationFactory(entity.RelationType);
- var dto = factory.BuildDto(entity);
+ var dto = RelationFactory.BuildDto(entity);
Database.Update(dto);
+ PopulateObjectTypes(entity);
+
entity.ResetDirtyProperties();
}
#endregion
+
+ ///
+ /// Used for joining the entity query with relations for the paging methods
+ ///
+ ///
+ private void SqlJoinRelations(Sql sql)
+ {
+ // add left joins for relation tables (this joins on both child or parent, so beware that this will normally return entities for
+ // both sides of the relation type unless the IUmbracoEntity query passed in filters one side out).
+ sql.LeftJoin().On((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId);
+ sql.LeftJoin().On((left, right) => left.RelationType == right.Id);
+ }
+
+ public IEnumerable GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
+ {
+ // var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
+ // we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
+ // required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it
+ // means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we
+ // will just return the bare minimum entity data.
+
+ return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
+ {
+ SqlJoinRelations(sql);
+
+ sql.Where(rel => rel.ChildId == childId);
+ sql.Where((rel, node) => rel.ParentId == childId || node.NodeId != childId);
+ });
+ }
+
+ public IEnumerable GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes)
+ {
+ // var contentObjectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member }
+ // we could pass in the contentObjectTypes so that the entity repository sql is configured to do full entity lookups so that we get the full data
+ // required to populate content, media or members, else we get the bare minimum data needed to populate an entity. BUT if we do this it
+ // means that the SQL is less efficient and returns data that is probably not needed for what we need this lookup for. For the time being we
+ // will just return the bare minimum entity data.
+
+ return _entityRepository.GetPagedResultsByQuery(Query(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
+ {
+ SqlJoinRelations(sql);
+
+ sql.Where(rel => rel.ParentId == parentId);
+ sql.Where((rel, node) => rel.ChildId == parentId || node.NodeId != parentId);
+ });
+ }
+
+ public void Save(IEnumerable relations)
+ {
+ foreach (var hasIdentityGroup in relations.GroupBy(r => r.HasIdentity))
+ {
+ if (hasIdentityGroup.Key)
+ {
+ // Do updates, we can't really do a bulk update so this is still a 1 by 1 operation
+ // however we can bulk populate the object types. It might be possible to bulk update
+ // with SQL but would be pretty ugly and we're not really too worried about that for perf,
+ // it's the bulk inserts we care about.
+ var asArray = hasIdentityGroup.ToArray();
+ foreach (var relation in hasIdentityGroup)
+ {
+ relation.UpdatingEntity();
+ var dto = RelationFactory.BuildDto(relation);
+ Database.Update(dto);
+ }
+ PopulateObjectTypes(asArray);
+ }
+ else
+ {
+ // Do bulk inserts
+ var entitiesAndDtos = hasIdentityGroup.ToDictionary(
+ r => // key = entity
+ {
+ r.AddingEntity();
+ return r;
+ },
+ RelationFactory.BuildDto); // value = DTO
+
+ // Use NPoco's own InsertBulk command which will automatically re-populate the new Ids on the entities, our own
+ // BulkInsertRecords does not cater for this.
+ Database.InsertBulk(entitiesAndDtos.Values);
+
+ // All dtos now have IDs assigned
+ foreach (var de in entitiesAndDtos)
+ {
+ // re-assign ID to the entity
+ de.Key.Id = de.Value.Id;
+ }
+
+ PopulateObjectTypes(entitiesAndDtos.Keys.ToArray());
+ }
+ }
+ }
+
+ public IEnumerable GetPagedRelationsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering)
+ {
+ var sql = GetBaseQuery(false);
+
+ if (ordering == null || ordering.IsEmpty)
+ ordering = Ordering.By(SqlSyntax.GetQuotedColumn(Constants.DatabaseSchema.Tables.Relation, "id"));
+
+ var translator = new SqlTranslator(sql, query);
+ sql = translator.Translate();
+
+ // apply ordering
+ ApplyOrdering(ref sql, ordering);
+
+ var pageIndexToFetch = pageIndex + 1;
+ var page = Database.Page(pageIndexToFetch, pageSize, sql);
+ var dtos = page.Items;
+ totalRecords = page.TotalItems;
+
+ var relTypes = _relationTypeRepository.GetMany(dtos.Select(x => x.RelationType).Distinct().ToArray())
+ .ToDictionary(x => x.Id, x => x);
+
+ var result = dtos.Select(r =>
+ {
+ if (!relTypes.TryGetValue(r.RelationType, out var relType))
+ throw new InvalidOperationException(string.Format("RelationType with Id: {0} doesn't exist", r.RelationType));
+ return DtoToEntity(r, relType);
+ }).ToList();
+
+ return result;
+ }
+
+
+ public void DeleteByParent(int parentId, params string[] relationTypeAliases)
+ {
+ var subQuery = Sql().Select(x => x.Id)
+ .From()
+ .InnerJoin().On(x => x.RelationType, x => x.Id)
+ .Where(x => x.ParentId == parentId);
+
+ if (relationTypeAliases.Length > 0)
+ {
+ subQuery.WhereIn(x => x.Alias, relationTypeAliases);
+ }
+
+ Database.Execute(Sql().Delete().WhereIn(x => x.Id, subQuery));
+ }
+
+ ///
+ /// Used to populate the object types after insert/update
+ ///
+ ///
+ private void PopulateObjectTypes(params IRelation[] entities)
+ {
+ var entityIds = entities.Select(x => x.ParentId).Concat(entities.Select(y => y.ChildId)).Distinct();
+
+ var nodes = Database.Fetch(Sql().Select().From()
+ .WhereIn(x => x.NodeId, entityIds))
+ .ToDictionary(x => x.NodeId, x => x.NodeObjectType);
+
+ foreach (var e in entities)
+ {
+ if (nodes.TryGetValue(e.ParentId, out var parentObjectType))
+ {
+ e.ParentObjectType = parentObjectType.GetValueOrDefault();
+ }
+ if (nodes.TryGetValue(e.ChildId, out var childObjectType))
+ {
+ e.ChildObjectType = childObjectType.GetValueOrDefault();
+ }
+ }
+ }
+
+ private void ApplyOrdering(ref Sql sql, Ordering ordering)
+ {
+ if (sql == null) throw new ArgumentNullException(nameof(sql));
+ if (ordering == null) throw new ArgumentNullException(nameof(ordering));
+
+ // TODO: although this works for name, it probably doesn't work for others without an alias of some sort
+ var orderBy = ordering.OrderBy;
+
+ if (ordering.Direction == Direction.Ascending)
+ sql.OrderBy(orderBy);
+ else
+ sql.OrderByDescending(orderBy);
+ }
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs
index 075d4aa769..623b55b6f8 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationTypeRepository.cs
@@ -134,7 +134,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistNewItem(IRelationType entity)
{
entity.AddingEntity();
-
+
+ CheckNullObjectTypeValues(entity);
+
var dto = RelationTypeFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
@@ -146,7 +148,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override void PersistUpdatedItem(IRelationType entity)
{
entity.UpdatingEntity();
-
+
+ CheckNullObjectTypeValues(entity);
+
var dto = RelationTypeFactory.BuildDto(entity);
Database.Update(dto);
@@ -154,5 +158,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
#endregion
+
+ private void CheckNullObjectTypeValues(IRelationType entity)
+ {
+ if (entity.ParentObjectType.HasValue && entity.ParentObjectType == Guid.Empty)
+ entity.ParentObjectType = null;
+ if (entity.ChildObjectType.HasValue && entity.ChildObjectType == Guid.Empty)
+ entity.ChildObjectType = null;
+ }
}
}
diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
index f7cf480830..b829f1fbc5 100644
--- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
+++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlSyntaxProviderExtensions.cs
@@ -35,6 +35,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
///
public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery, WhereInType whereInType = WhereInType.In)
{
+ //TODO: This is no longer necessary since this used to be a specific requirement for MySql!
+ // Now we can do a Delete + sub query, see RelationRepository.DeleteByParent for example
return
new Sql(string.Format(
diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
index 072813b4e6..a95d95ea08 100644
--- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
+++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
@@ -44,6 +44,8 @@ namespace Umbraco.Core.Persistence
_commandRetryPolicy = commandRetryPolicy;
EnableSqlTrace = EnableSqlTraceDefault;
+
+ NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
///
@@ -57,6 +59,8 @@ namespace Umbraco.Core.Persistence
_logger = logger;
EnableSqlTrace = EnableSqlTraceDefault;
+
+ NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
#endregion
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditor.cs b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
index 047c0f0683..10f2c3f57b 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditor.cs
@@ -19,6 +19,7 @@ namespace Umbraco.Core.PropertyEditors
public class DataEditor : IDataEditor
{
private IDictionary _defaultConfiguration;
+ private IDataValueEditor _dataValueEditor;
///
/// Initializes a new instance of the class.
@@ -90,7 +91,7 @@ namespace Umbraco.Core.PropertyEditors
/// simple enough for now.
///
// TODO: point of that one? shouldn't we always configure?
- public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
+ public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_dataValueEditor ?? (_dataValueEditor = CreateValueEditor()));
///
///
@@ -113,7 +114,7 @@ namespace Umbraco.Core.PropertyEditors
return ExplicitValueEditor;
var editor = CreateValueEditor();
- ((DataValueEditor) editor).Configuration = configuration; // TODO: casting is bad
+ ((DataValueEditor)editor).Configuration = configuration; // TODO: casting is bad
return editor;
}
@@ -163,7 +164,7 @@ namespace Umbraco.Core.PropertyEditors
protected virtual IDataValueEditor CreateValueEditor()
{
if (Attribute == null)
- throw new InvalidOperationException("The editor does not specify a view.");
+ throw new InvalidOperationException($"The editor is not attributed with {nameof(DataEditorAttribute)}");
return new DataValueEditor(Current.Services.DataTypeService, Current.Services.LocalizationService, Attribute);
}
@@ -175,7 +176,7 @@ namespace Umbraco.Core.PropertyEditors
{
var editor = new ConfigurationEditor();
// pass the default configuration if this is not a property value editor
- if((Type & EditorType.PropertyValue) == 0)
+ if ((Type & EditorType.PropertyValue) == 0)
{
editor.DefaultConfiguration = _defaultConfiguration;
}
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
index afd77a3479..61f3e3a7c2 100644
--- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs
@@ -273,12 +273,14 @@ namespace Umbraco.Core.PropertyEditors
}
}
+
// TODO: the methods below should be replaced by proper property value convert ToXPath usage!
///
/// Converts a property to Xml fragments.
///
- public IEnumerable ConvertDbToXml(IProperty property, bool published)
+ public IEnumerable ConvertDbToXml(IProperty property, IDataTypeService dataTypeService,
+ ILocalizationService localizationService, bool published)
{
published &= property.PropertyType.SupportsPublishing;
@@ -296,7 +298,7 @@ namespace Umbraco.Core.PropertyEditors
if (pvalue.Segment != null)
xElement.Add(new XAttribute("segment", pvalue.Segment));
- var xValue = ConvertDbToXml(property.PropertyType, value);
+ var xValue = ConvertDbToXml(property.PropertyType, value, dataTypeService);
xElement.Add(xValue);
yield return xElement;
@@ -312,12 +314,12 @@ namespace Umbraco.Core.PropertyEditors
/// Returns an XText or XCData instance which must be wrapped in a element.
/// If the value is empty we will not return as CDATA since that will just take up more space in the file.
///
- public XNode ConvertDbToXml(IPropertyType propertyType, object value)
+ public XNode ConvertDbToXml(IPropertyType propertyType, object value, IDataTypeService dataTypeService)
{
//check for null or empty value, we don't want to return CDATA if that is the case
if (value == null || value.ToString().IsNullOrWhiteSpace())
{
- return new XText(ConvertDbToString(propertyType, value));
+ return new XText(ConvertDbToString(propertyType, value, dataTypeService));
}
switch (ValueTypes.ToStorageType(ValueType))
@@ -325,11 +327,11 @@ namespace Umbraco.Core.PropertyEditors
case ValueStorageType.Date:
case ValueStorageType.Integer:
case ValueStorageType.Decimal:
- return new XText(ConvertDbToString(propertyType, value));
+ return new XText(ConvertDbToString(propertyType, value, dataTypeService));
case ValueStorageType.Nvarchar:
case ValueStorageType.Ntext:
//put text in cdata
- return new XCData(ConvertDbToString(propertyType, value));
+ return new XCData(ConvertDbToString(propertyType, value, dataTypeService));
default:
throw new ArgumentOutOfRangeException();
}
@@ -338,7 +340,7 @@ namespace Umbraco.Core.PropertyEditors
///
/// Converts a property value to a string.
///
- public virtual string ConvertDbToString(IPropertyType propertyType, object value)
+ public virtual string ConvertDbToString(IPropertyType propertyType, object value, IDataTypeService dataTypeService)
{
if (value == null)
return string.Empty;
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
new file mode 100644
index 0000000000..51dfe6c5c4
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Editors;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ public class DataValueReferenceFactoryCollection : BuilderCollectionBase
+ {
+ public DataValueReferenceFactoryCollection(IEnumerable items)
+ : base(items)
+ { }
+
+ public IEnumerable GetAllReferences(IPropertyCollection properties, PropertyEditorCollection propertyEditors)
+ {
+ var trackedRelations = new List();
+
+ foreach (var p in properties)
+ {
+ if (!propertyEditors.TryGet(p.PropertyType.PropertyEditorAlias, out var editor)) continue;
+
+ //TODO: Support variants/segments! This is not required for this initial prototype which is why there is a check here
+ if (!p.PropertyType.VariesByNothing()) continue;
+ var val = p.GetValue(); // get the invariant value
+
+ var valueEditor = editor.GetValueEditor();
+ if (valueEditor is IDataValueReference reference)
+ {
+ var refs = reference.GetReferences(val);
+ trackedRelations.AddRange(refs);
+ }
+
+ // Loop over collection that may be add to existing property editors
+ // implementation of GetReferences in IDataValueReference.
+ // Allows developers to add support for references by a
+ // package /property editor that did not implement IDataValueReference themselves
+ foreach (var item in this)
+ {
+ // Check if this value reference is for this datatype/editor
+ // Then call it's GetReferences method - to see if the value stored
+ // in the dataeditor/property has referecnes to media/content items
+ if (item.IsForEditor(editor))
+ trackedRelations.AddRange(item.GetDataValueReference().GetReferences(val));
+ }
+ }
+
+ return trackedRelations;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs
new file mode 100644
index 0000000000..2cf76712c8
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionBuilder.cs
@@ -0,0 +1,9 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase
+ {
+ protected override DataValueReferenceFactoryCollectionBuilder This => this;
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs
new file mode 100644
index 0000000000..6377098bfc
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/IDataValueReference.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models.Editors;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ ///
+ /// Resolve references from values
+ ///
+ public interface IDataValueReference
+ {
+ ///
+ /// Returns any references contained in the value
+ ///
+ ///
+ ///
+ IEnumerable GetReferences(object value);
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs
new file mode 100644
index 0000000000..6587e071bf
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/IDataValueReferenceFactory.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Core.PropertyEditors
+{
+ public interface IDataValueReferenceFactory
+ {
+ ///
+ /// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor).
+ ///
+ /// The datatype.
+ /// A value indicating whether the converter supports a datatype.
+ bool IsForEditor(IDataEditor dataEditor);
+
+ ///
+ ///
+ ///
+ ///
+ IDataValueReference GetDataValueReference();
+ }
+}
diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
index 9373f7b70c..1e145b33d6 100644
--- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
@@ -47,7 +47,7 @@ namespace Umbraco.Core.Runtime
// register persistence mappers - required by database factory so needs to be done here
// means the only place the collection can be modified is in a runtime - afterwards it
// has been frozen and it is too late
- composition.WithCollectionBuilder().AddCoreMappers();
+ composition.Mappers().AddCoreMappers();
// register the scope provider
composition.RegisterUnique(); // implements both IScopeProvider and IScopeAccessor
@@ -76,11 +76,15 @@ namespace Umbraco.Core.Runtime
composition.ManifestFilters();
// properties and parameters derive from data editors
- composition.WithCollectionBuilder()
+ composition.DataEditors()
.Add(() => composition.TypeLoader.GetDataEditors());
composition.RegisterUnique();
composition.RegisterUnique();
+ // Used to determine if a datatype/editor should be storing/tracking
+ // references to media item/s
+ composition.DataValueReferenceFactories();
+
// register a server registrar, by default it's the db registrar
composition.RegisterUnique(f =>
{
@@ -109,13 +113,13 @@ namespace Umbraco.Core.Runtime
factory.GetInstance()
));
- composition.WithCollectionBuilder()
+ composition.CacheRefreshers()
.Add(() => composition.TypeLoader.GetCacheRefreshers());
- composition.WithCollectionBuilder()
+ composition.PackageActions()
.Add(() => composition.TypeLoader.GetPackageActions());
- composition.WithCollectionBuilder()
+ composition.PropertyValueConverters()
.Append(composition.TypeLoader.GetTypes());
composition.RegisterUnique();
@@ -123,7 +127,7 @@ namespace Umbraco.Core.Runtime
composition.RegisterUnique(factory
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance())));
- composition.WithCollectionBuilder()
+ composition.UrlSegmentProviders()
.Append();
composition.RegisterUnique(factory => new MigrationBuilder(factory));
diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
index 8c7297b552..9e0c8d2613 100644
--- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
+++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs
@@ -564,7 +564,7 @@ namespace Umbraco.Core.Services.Implement
var propertyEditor = Current.PropertyEditors[propertyType.PropertyEditorAlias];
return propertyEditor == null
? Array.Empty()
- : propertyEditor.GetValueEditor().ConvertDbToXml(property, published);
+ : propertyEditor.GetValueEditor().ConvertDbToXml(property, _dataTypeService, _localizationService, published);
}
// exports an IContent item descendants.
diff --git a/src/Umbraco.Core/Services/Implement/RelationService.cs b/src/Umbraco.Core/Services/Implement/RelationService.cs
index 405c3a2800..4b53709de9 100644
--- a/src/Umbraco.Core/Services/Implement/RelationService.cs
+++ b/src/Umbraco.Core/Services/Implement/RelationService.cs
@@ -25,11 +25,7 @@ namespace Umbraco.Core.Services.Implement
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
}
- ///
- /// Gets a by its Id
- ///
- /// Id of the
- /// A object
+ ///
public IRelation GetById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -38,11 +34,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets a by its Id
- ///
- /// Id of the
- /// A object
+ ///
public IRelationType GetRelationTypeById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -51,11 +43,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets a by its Id
- ///
- /// Id of the
- /// A object
+ ///
public IRelationType GetRelationTypeById(Guid id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -64,25 +52,10 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets a by its Alias
- ///
- /// Alias of the
- /// A object
- public IRelationType GetRelationTypeByAlias(string alias)
- {
- using (var scope = ScopeProvider.CreateScope(autoComplete: true))
- {
- var query = Query().Where(x => x.Alias == alias);
- return _relationTypeRepository.Get(query).FirstOrDefault();
- }
- }
+ ///
+ public IRelationType GetRelationTypeByAlias(string alias) => GetRelationType(alias);
- ///
- /// Gets all objects
- ///
- /// Optional array of integer ids to return relations for
- /// An enumerable list of objects
+ ///
public IEnumerable GetAllRelations(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -91,21 +64,13 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets all objects by their
- ///
- /// to retrieve Relations for
- /// An enumerable list of objects
- public IEnumerable GetAllRelationsByRelationType(RelationType relationType)
+ ///
+ public IEnumerable GetAllRelationsByRelationType(IRelationType relationType)
{
return GetAllRelationsByRelationType(relationType.Id);
}
- ///
- /// Gets all objects by their 's Id
- ///
- /// Id of the to retrieve Relations for
- /// An enumerable list of objects
+ ///
public IEnumerable GetAllRelationsByRelationType(int relationTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -115,11 +80,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets all objects
- ///
- /// Optional array of integer ids to return relationtypes for
- /// An enumerable list of objects
+ ///
public IEnumerable GetAllRelationTypes(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -128,82 +89,65 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets a list of objects by their parent Id
- ///
- /// Id of the parent to retrieve relations for
- /// An enumerable list of objects
- public IEnumerable GetByParentId(int id)
+ ///
+ public IEnumerable GetByParentId(int id) => GetByParentId(id, null);
+
+ ///
+ public IEnumerable GetByParentId(int id, string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
- var query = Query().Where(x => x.ParentId == id);
- return _relationRepository.Get(query);
+ if (relationTypeAlias.IsNullOrWhiteSpace())
+ {
+ var qry1 = Query().Where(x => x.ParentId == id);
+ return _relationRepository.Get(qry1);
+ }
+
+ var relationType = GetRelationType(relationTypeAlias);
+ if (relationType == null)
+ return Enumerable.Empty();
+
+ var qry2 = Query().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id);
+ return _relationRepository.Get(qry2);
}
}
- ///
- /// Gets a list of objects by their parent entity
- ///
- /// Parent Entity to retrieve relations for
- /// An enumerable list of objects
- public IEnumerable GetByParent(IUmbracoEntity parent)
- {
- return GetByParentId(parent.Id);
- }
+ ///
+ public IEnumerable GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id);
- ///
- /// Gets a list of objects by their parent entity
- ///
- /// Parent Entity to retrieve relations for
- /// Alias of the type of relation to retrieve
- /// An enumerable list of objects
- public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias)
- {
- return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias);
- }
+ ///
+ public IEnumerable GetByParent(IUmbracoEntity parent, string relationTypeAlias) => GetByParentId(parent.Id, relationTypeAlias);
- ///
- /// Gets a list of objects by their child Id
- ///
- /// Id of the child to retrieve relations for
- /// An enumerable list of objects
- public IEnumerable GetByChildId(int id)
+ ///
+ public IEnumerable GetByChildId(int id) => GetByChildId(id, null);
+
+ ///
+ public IEnumerable GetByChildId(int id, string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
- var query = Query().Where(x => x.ChildId == id);
- return _relationRepository.Get(query);
+ if (relationTypeAlias.IsNullOrWhiteSpace())
+ {
+ var qry1 = Query().Where(x => x.ChildId == id);
+ return _relationRepository.Get(qry1);
+ }
+
+ var relationType = GetRelationType(relationTypeAlias);
+ if (relationType == null)
+ return Enumerable.Empty();
+
+ var qry2 = Query().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id);
+ return _relationRepository.Get(qry2);
}
}
- ///
- /// Gets a list of objects by their child Entity
- ///
- /// Child Entity to retrieve relations for
- /// An enumerable list of objects
- public IEnumerable GetByChild(IUmbracoEntity child)
- {
- return GetByChildId(child.Id);
- }
+ ///
+ public IEnumerable GetByChild(IUmbracoEntity child) => GetByChildId(child.Id);
- ///
- /// Gets a list of objects by their child Entity
- ///
- /// Child Entity to retrieve relations for
- /// Alias of the type of relation to retrieve
- /// An enumerable list of objects
- public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias)
- {
- return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias);
- }
+ ///
+ public IEnumerable GetByChild(IUmbracoEntity child, string relationTypeAlias) => GetByChildId(child.Id, relationTypeAlias);
- ///
- /// Gets a list of objects by their child or parent Id.
- /// Using this method will get you all relations regards of it being a child or parent relation.
- ///
- /// Id of the child or parent to retrieve relations for
- /// An enumerable list of objects
+ ///
public IEnumerable GetByParentOrChildId(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -217,8 +161,7 @@ namespace Umbraco.Core.Services.Implement
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
- var rtQuery = Query().Where(x => x.Alias == relationTypeAlias);
- var relationType = _relationTypeRepository.Get(rtQuery).FirstOrDefault();
+ var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty();
@@ -227,16 +170,13 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets a list of objects by the Name of the
- ///
- /// Name of the to retrieve Relations for
- /// An enumerable list of objects
+ ///
public IEnumerable GetByRelationTypeName(string relationTypeName)
{
List relationTypeIds;
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
+ //This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd.
var query = Query().Where(x => x.Name == relationTypeName);
var relationTypes = _relationTypeRepository.Get(query);
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
@@ -247,31 +187,17 @@ namespace Umbraco.Core.Services.Implement
: GetRelationsByListOfTypeIds(relationTypeIds);
}
- ///
- /// Gets a list of objects by the Alias of the
- ///
- /// Alias of the to retrieve Relations for
- /// An enumerable list of objects
+ ///
public IEnumerable GetByRelationTypeAlias(string relationTypeAlias)
{
- List relationTypeIds;
- using (var scope = ScopeProvider.CreateScope(autoComplete: true))
- {
- var query = Query().Where(x => x.Alias == relationTypeAlias);
- var relationTypes = _relationTypeRepository.Get(query);
- relationTypeIds = relationTypes.Select(x => x.Id).ToList();
- }
-
- return relationTypeIds.Count == 0
+ var relationType = GetRelationType(relationTypeAlias);
+
+ return relationType == null
? Enumerable.Empty()
- : GetRelationsByListOfTypeIds(relationTypeIds);
+ : GetRelationsByListOfTypeIds(new[] { relationType.Id });
}
- ///
- /// Gets a list of objects by the Id of the
- ///
- /// Id of the to retrieve Relations for
- /// An enumerable list of objects
+ ///
public IEnumerable GetByRelationTypeId(int relationTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -281,37 +207,35 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Gets the Child object from a Relation as an
- ///
- /// Relation to retrieve child object from
- /// An
+ ///
+ public IEnumerable GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ var query = Query().Where(x => x.RelationTypeId == relationTypeId);
+ return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering);
+ }
+ }
+
+ ///
public IUmbracoEntity GetChildEntityFromRelation(IRelation relation)
{
- var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
+ var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
return _entityService.Get(relation.ChildId, objectType);
}
- ///
- /// Gets the Parent object from a Relation as an
- ///
- /// Relation to retrieve parent object from
- /// An
+ ///
public IUmbracoEntity GetParentEntityFromRelation(IRelation relation)
{
- var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+ var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
return _entityService.Get(relation.ParentId, objectType);
}
- ///
- /// Gets the Parent and Child objects from a Relation as a "/> with .
- ///
- /// Relation to retrieve parent and child object from
- /// Returns a Tuple with Parent (item1) and Child (item2)
+ ///
public Tuple GetEntitiesFromRelation(IRelation relation)
{
- var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
- var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+ var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
+ var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
var child = _entityService.Get(relation.ChildId, childObjectType);
var parent = _entityService.Get(relation.ParentId, parentObjectType);
@@ -319,45 +243,63 @@ namespace Umbraco.Core.Services.Implement
return new Tuple(parent, child);
}
- ///
- /// Gets the Child objects from a list of Relations as a list of objects.
- ///
- /// List of relations to retrieve child objects from
- /// An enumerable list of
+ ///
public IEnumerable GetChildEntitiesFromRelations(IEnumerable relations)
{
- foreach (var relation in relations)
+ // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll
+ // method to lookup batches of entities for each parent object type
+
+ foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType)))
{
- var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
- yield return _entityService.Get(relation.ChildId, objectType);
+ var objectType = groupedRelations.Key;
+ var ids = groupedRelations.Select(x => x.ChildId).ToArray();
+ foreach (var e in _entityService.GetAll(objectType, ids))
+ yield return e;
}
}
- ///
- /// Gets the Parent objects from a list of Relations as a list of objects.
- ///
- /// List of relations to retrieve parent objects from
- /// An enumerable list of
+ ///
public IEnumerable GetParentEntitiesFromRelations(IEnumerable relations)
{
- foreach (var relation in relations)
+ // Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll
+ // method to lookup batches of entities for each parent object type
+
+ foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType)))
{
- var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
- yield return _entityService.Get(relation.ParentId, objectType);
+ var objectType = groupedRelations.Key;
+ var ids = groupedRelations.Select(x => x.ParentId).ToArray();
+ foreach (var e in _entityService.GetAll(objectType, ids))
+ yield return e;
}
}
- ///
- /// Gets the Parent and Child objects from a list of Relations as a list of objects.
- ///
- /// List of relations to retrieve parent and child objects from
- /// An enumerable list of with
+ ///
+ public IEnumerable GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray());
+ }
+ }
+
+ ///
+ public IEnumerable GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray());
+ }
+ }
+
+ ///
public IEnumerable> GetEntitiesFromRelations(IEnumerable relations)
{
+ //TODO: Argh! N+1
+
foreach (var relation in relations)
{
- var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
- var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
+ var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
+ var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
var child = _entityService.Get(relation.ChildId, childObjectType);
var parent = _entityService.Get(relation.ParentId, parentObjectType);
@@ -366,19 +308,15 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Relates two objects by their entity Ids.
- ///
- /// Id of the parent
- /// Id of the child
- /// The type of relation to create
- /// The created
+ ///
public IRelation Relate(int parentId, int childId, IRelationType relationType)
{
// Ensure that the RelationType has an identity before using it to relate two entities
if (relationType.HasIdentity == false)
Save(relationType);
+ //TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok?
+
var relation = new Relation(parentId, childId, relationType);
using (var scope = ScopeProvider.CreateScope())
@@ -398,25 +336,13 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Relates two objects that are based on the interface.
- ///
- /// Parent entity
- /// Child entity
- /// The type of relation to create
- /// The created
+ ///
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType)
{
return Relate(parent.Id, child.Id, relationType);
}
- ///
- /// Relates two objects by their entity Ids.
- ///
- /// Id of the parent
- /// Id of the child
- /// Alias of the type of relation to create
- /// The created
+ ///
public IRelation Relate(int parentId, int childId, string relationTypeAlias)
{
var relationType = GetRelationTypeByAlias(relationTypeAlias);
@@ -426,13 +352,7 @@ namespace Umbraco.Core.Services.Implement
return Relate(parentId, childId, relationType);
}
- ///
- /// Relates two objects that are based on the interface.
- ///
- /// Parent entity
- /// Child entity
- /// Alias of the type of relation to create
- /// The created
+ ///
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
{
var relationType = GetRelationTypeByAlias(relationTypeAlias);
@@ -442,11 +362,7 @@ namespace Umbraco.Core.Services.Implement
return Relate(parent.Id, child.Id, relationType);
}
- ///
- /// Checks whether any relations exists for the passed in .
- ///
- /// to check for relations
- /// Returns True if any relations exists for the given , otherwise False
+ ///
public bool HasRelations(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -456,11 +372,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Checks whether any relations exists for the passed in Id.
- ///
- /// Id of an object to check relations for
- /// Returns True if any relations exists with the given Id, otherwise False
+ ///
public bool IsRelated(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -470,12 +382,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Checks whether two items are related
- ///
- /// Id of the Parent relation
- /// Id of the Child relation
- /// Returns True if any relations exists with the given Ids, otherwise False
+ ///
public bool AreRelated(int parentId, int childId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -485,13 +392,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Checks whether two items are related with a given relation type alias
- ///
- /// Id of the Parent relation
- /// Id of the Child relation
- /// Alias of the relation type
- /// Returns True if any relations exists with the given Ids and relation type, otherwise False
+ ///
public bool AreRelated(int parentId, int childId, string relationTypeAlias)
{
var relType = GetRelationTypeByAlias(relationTypeAlias);
@@ -502,13 +403,7 @@ namespace Umbraco.Core.Services.Implement
}
- ///
- /// Checks whether two items are related with a given relation type
- ///
- /// Id of the Parent relation
- /// Id of the Child relation
- /// Type of relation
- /// Returns True if any relations exists with the given Ids and relation type, otherwise False
+ ///
public bool AreRelated(int parentId, int childId, IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -518,34 +413,20 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Checks whether two items are related
- ///
- /// Parent entity
- /// Child entity
- /// Returns True if any relations exist between the entities, otherwise False
+ ///
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child)
{
return AreRelated(parent.Id, child.Id);
}
- ///
- /// Checks whether two items are related
- ///
- /// Parent entity
- /// Child entity
- /// Alias of the type of relation to create
- /// Returns True if any relations exist between the entities, otherwise False
+ ///
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
{
return AreRelated(parent.Id, child.Id, relationTypeAlias);
}
- ///
- /// Saves a
- ///
- /// Relation to save
+ ///
public void Save(IRelation relation)
{
using (var scope = ScopeProvider.CreateScope())
@@ -564,10 +445,25 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Saves a
- ///
- /// RelationType to Save
+ public void Save(IEnumerable relations)
+ {
+ using (var scope = ScopeProvider.CreateScope())
+ {
+ var saveEventArgs = new SaveEventArgs(relations);
+ if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
+ {
+ scope.Complete();
+ return;
+ }
+
+ _relationRepository.Save(relations);
+ scope.Complete();
+ saveEventArgs.CanCancel = false;
+ scope.Events.Dispatch(SavedRelation, this, saveEventArgs);
+ }
+ }
+
+ ///
public void Save(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope())
@@ -586,10 +482,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Deletes a
- ///
- /// Relation to Delete
+ ///
public void Delete(IRelation relation)
{
using (var scope = ScopeProvider.CreateScope())
@@ -608,10 +501,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Deletes a
- ///
- /// RelationType to Delete
+ ///
public void Delete(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope())
@@ -630,10 +520,7 @@ namespace Umbraco.Core.Services.Implement
}
}
- ///
- /// Deletes all objects based on the passed in
- ///
- /// to Delete Relations for
+ ///
public void DeleteRelationsOfType(IRelationType relationType)
{
var relations = new List();
@@ -642,6 +529,8 @@ namespace Umbraco.Core.Services.Implement
var query = Query().Where(x => x.RelationTypeId == relationType.Id);
relations.AddRange(_relationRepository.Get(query).ToList());
+ //TODO: N+1, we should be able to do this in a single call
+
foreach (var relation in relations)
_relationRepository.Delete(relation);
@@ -653,6 +542,15 @@ namespace Umbraco.Core.Services.Implement
#region Private Methods
+ private IRelationType GetRelationType(string relationTypeAlias)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ var query = Query().Where(x => x.Alias == relationTypeAlias);
+ return _relationTypeRepository.Get(query).FirstOrDefault();
+ }
+ }
+
private IEnumerable