diff --git a/.gitattributes b/.gitattributes
index a664be3a85..c8987ade67 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -13,7 +13,7 @@
*.png binary
*.gif binary
-*.cs text=auto diff=csharp
+*.cs text=auto diff=csharp
*.vb text=auto
*.c text=auto
*.cpp text=auto
@@ -41,9 +41,13 @@
*.fs text=auto
*.fsx text=auto
*.hs text=auto
+*.json text=auto
+*.xml text=auto
-*.csproj text=auto merge=union
-*.vbproj text=auto merge=union
-*.fsproj text=auto merge=union
-*.dbproj text=auto merge=union
-*.sln text=auto eol=crlf merge=union
+*.csproj text=auto merge=union
+*.vbproj text=auto merge=union
+*.fsproj text=auto merge=union
+*.dbproj text=auto merge=union
+*.sln text=auto eol=crlf merge=union
+
+*.gitattributes text=auto
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e009ee2294..cea5859486 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -60,10 +60,10 @@ Great question! The short version goes like this:

- * **Switch to the correct branch** - switch to the v8-dev branch
+ * **Switch to the correct branch** - switch to the `v8/contrib` branch
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
- * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first.
+ * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first.
* **Push** - great, now you can push the changes up to your fork on GitHub
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
@@ -158,7 +158,7 @@ To find the general areas for something you're looking to fix or improve, have a
### Which branch should I target for my contributions?
-We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
+We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
@@ -184,10 +184,10 @@ Then when you want to get the changes from the main repository:
```
git fetch upstream
-git rebase upstream/v8/dev
+git rebase upstream/v8/contrib
```
-In this command we're syncing with the `v8/dev` branch, but you can of course choose another one if needed.
+In this command we're syncing with the `v8/contrib` branch, but you can of course choose another one if needed.
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
diff --git a/.github/README.md b/.github/README.md
index d6d978c3d6..467ca6e5e6 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -1,4 +1,4 @@
-# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
+# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
diff --git a/.github/img/defaultbranch.png b/.github/img/defaultbranch.png
index f3a5b9efbc..3550b5c34c 100644
Binary files a/.github/img/defaultbranch.png and b/.github/img/defaultbranch.png differ
diff --git a/.gitignore b/.gitignore
index a0ff4d5b27..12ad3299ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,6 +55,7 @@ App_Data/TEMP/*
src/Umbraco.Web.UI/[Cc]ss/*
src/Umbraco.Web.UI/App_Code/*
src/Umbraco.Web.UI/App_Data/*
+src/Umbraco.Web.UI/data/*
src/Umbraco.Tests/App_Data/*
src/Umbraco.Web.UI/[Mm]edia/*
src/Umbraco.Web.UI/[Mm]aster[Pp]ages/*
diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index 658d2f0672..347bde139e 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -28,7 +28,7 @@
-
+
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/ComponentCollection.cs b/src/Umbraco.Core/Composing/ComponentCollection.cs
index fa4a1849b6..62b240f10f 100644
--- a/src/Umbraco.Core/Composing/ComponentCollection.cs
+++ b/src/Umbraco.Core/Composing/ComponentCollection.cs
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Composing
}
catch (Exception ex)
{
- _logger.Error(componentType, ex, "Error while terminating component {ComponentType}.", componentType.FullName);
+ _logger.Error(ex, "Error while terminating component {ComponentType}.", componentType.FullName);
}
}
}
diff --git a/src/Umbraco.Core/Composing/Current.cs b/src/Umbraco.Core/Composing/Current.cs
index f12bf0dd63..846331a16e 100644
--- a/src/Umbraco.Core/Composing/Current.cs
+++ b/src/Umbraco.Core/Composing/Current.cs
@@ -154,6 +154,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 5dd33c2a60..ced9a9386a 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/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs
index e78c498e66..c1d7103a1c 100644
--- a/src/Umbraco.Core/Constants-Conventions.cs
+++ b/src/Umbraco.Core/Constants-Conventions.cs
@@ -315,34 +315,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.Core/UdiEntityType.cs b/src/Umbraco.Core/Contants-UdiEntityType.cs
similarity index 100%
rename from src/Umbraco.Core/UdiEntityType.cs
rename to src/Umbraco.Core/Contants-UdiEntityType.cs
diff --git a/src/Umbraco.Core/ContentVariationExtensions.cs b/src/Umbraco.Core/ContentVariationExtensions.cs
index 6430c75686..8939f7133b 100644
--- a/src/Umbraco.Core/ContentVariationExtensions.cs
+++ b/src/Umbraco.Core/ContentVariationExtensions.cs
@@ -27,6 +27,11 @@ namespace Umbraco.Core
///
public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing();
+ ///
+ /// Determines whether the content type varies by segment.
+ ///
+ public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment();
+
///
/// Determines whether the content type is invariant.
///
diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
index 94d8cfbc62..888ff9a632 100644
--- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs
@@ -310,14 +310,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 3b2005bef6..dfd85b4cfc 100644
--- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs
@@ -183,7 +183,9 @@ namespace Umbraco.Core.Migrations.Upgrade
To("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
- // to 8.6.0
+ // to 8.6.0...
+ To("{4759A294-9860-46BC-99F9-B4C975CAE580}");
+ To("{0BC866BC-0665-487A-9913-0290BD0169AD}");
To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");
To("{EE288A91-531B-4995-8179-1D62D9AA3E2E}");
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/ContentPropertyFile.cs b/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs
index ac236e1fdd..225e29a8a1 100644
--- a/src/Umbraco.Core/Models/Editors/ContentPropertyFile.cs
+++ b/src/Umbraco.Core/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.Core/Models/Editors/UmbracoEntityReference.cs b/src/Umbraco.Core/Models/Editors/UmbracoEntityReference.cs
new file mode 100644
index 0000000000..31d48e60cf
--- /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(Udi.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/Entities/ITreeEntity.cs b/src/Umbraco.Core/Models/Entities/ITreeEntity.cs
index afa3399202..ab63e1e1d8 100644
--- a/src/Umbraco.Core/Models/Entities/ITreeEntity.cs
+++ b/src/Umbraco.Core/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.Core/Models/Entities/MemberEntitySlim.cs b/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs
index 335e269467..338f363856 100644
--- a/src/Umbraco.Core/Models/Entities/MemberEntitySlim.cs
+++ b/src/Umbraco.Core/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.Core/Models/IRelation.cs b/src/Umbraco.Core/Models/IRelation.cs
index 745216fba1..6bd348d72f 100644
--- a/src/Umbraco.Core/Models/IRelation.cs
+++ b/src/Umbraco.Core/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.Core/Models/IRelationType.cs b/src/Umbraco.Core/Models/IRelationType.cs
index 8bbe657427..9253fae8ab 100644
--- a/src/Umbraco.Core/Models/IRelationType.cs
+++ b/src/Umbraco.Core/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.Core/Models/MediaTypeExtensions.cs b/src/Umbraco.Core/Models/MediaTypeExtensions.cs
new file mode 100644
index 0000000000..4e2ae5822a
--- /dev/null
+++ b/src/Umbraco.Core/Models/MediaTypeExtensions.cs
@@ -0,0 +1,10 @@
+namespace Umbraco.Core.Models
+{
+ internal static class MediaTypeExtensions
+ {
+ internal static bool IsSystemMediaType(this IMediaType mediaType) =>
+ mediaType.Alias == Constants.Conventions.MediaTypes.File
+ || mediaType.Alias == Constants.Conventions.MediaTypes.Folder
+ || mediaType.Alias == Constants.Conventions.MediaTypes.Image;
+ }
+}
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/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs
index f5d13c70c4..7afa476226 100644
--- a/src/Umbraco.Core/Models/Relation.cs
+++ b/src/Umbraco.Core/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.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs
index 725628bf90..1085ecdcdd 100644
--- a/src/Umbraco.Core/Models/RelationType.cs
+++ b/src/Umbraco.Core/Models/RelationType.cs
@@ -14,28 +14,26 @@ 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)
+ //TODO: Should we put back the broken ctors with obsolete attributes?
+
+ 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 +70,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 +81,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.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/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
index 69b0698a96..254e04d2d5 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
@@ -26,5 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
bool HasContainerInPath(string contentPath);
+
+ ///
+ /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
+ ///
+ bool HasContentNodes(int id);
}
}
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/IRelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs
index 51d7656d8a..fc1be20e6f 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IRelationRepository.cs
+++ b/src/Umbraco.Core/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.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 7ab73f3f2d..13b687eb4e 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.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
@@ -24,26 +25,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
@@ -797,5 +820,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/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index e2c3d8c9b5..6f714ff187 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -1323,6 +1323,17 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT
return Database.ExecuteScalar(sql) > 0;
}
+ ///
+ /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
+ ///
+ public bool HasContentNodes(int id)
+ {
+ var sql = new Sql(
+ $"SELECT CASE WHEN EXISTS (SELECT * FROM {Constants.DatabaseSchema.Tables.Content} WHERE contentTypeId = @id) THEN 1 ELSE 0 END",
+ new { id });
+ return Database.ExecuteScalar(sql) == 1;
+ }
+
protected override IEnumerable GetDeleteClauses()
{
// in theory, services should have ensured that content items of the given content type
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 820896c004..ccfa8209fb 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -11,6 +11,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;
@@ -29,8 +30,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));
@@ -74,9 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ids.Any())
sql.WhereIn(x => x.NodeId, ids);
- return MapDtosToContent(Database.Fetch(sql), false,
- // load everything
- true, true, true, true);
+ return MapDtosToContent(Database.Fetch(sql));
}
protected override IEnumerable PerformGetByQuery(IQuery query)
@@ -88,9 +102,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
AddGetByQueryOrderBy(sql);
- return MapDtosToContent(Database.Fetch(sql), false,
- // load everything
- true, true, true, true);
+ return MapDtosToContent(Database.Fetch(sql));
}
private void AddGetByQueryOrderBy(Sql sql)
@@ -229,9 +241,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.OrderByDescending(x => x.Current)
.AndByDescending(x => x.VersionDate);
- return MapDtosToContent(Database.Fetch(sql), true, true, true, true, true);
+ return MapDtosToContent(Database.Fetch(sql), true);
}
+ // TODO: This method needs to return a readonly version of IContent! The content returned
+ // from this method does not contain all of the data required to re-persist it and if that
+ // is attempted some odd things will occur.
+ // Either we create an IContentReadOnly (which ultimately we should for vNext so we can
+ // differentiate between methods that return entities that can be re-persisted or not), or
+ // in the meantime to not break API compatibility, we can add a property to IContentBase
+ // (or go further and have it on IUmbracoEntity): "IsReadOnly" and if that is true we throw
+ // an exception if that entity is passed to a Save method.
+ // Ideally we return "Slim" versions of content for all sorts of methods here and in ContentService.
+ // Perhaps another non-breaking alternative is to have new services like IContentServiceReadOnly
+ // which can return IContentReadOnly.
+ // We have the ability with `MapDtosToContent` to reduce the amount of data looked up for a
+ // content item. Ideally for paged data that populates list views, these would be ultra slim
+ // content items, there's no reason to populate those with really anything apart from property data,
+ // but until we do something like the above, we can't do that since it would be breaking and unclear.
public override IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take)
{
var sql = GetBaseQuery(QueryType.Many, false)
@@ -240,7 +267,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.AndByDescending(x => x.VersionDate);
return MapDtosToContent(Database.Fetch(sql), true,
- // load bare minimum
+ // load bare minimum, need variants though since this is used to rollback with variants
false, false, false, true).Skip(skip).Take(take);
}
@@ -473,6 +500,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
+ PersistRelations(entity);
+
entity.ResetDirtyProperties();
// troubleshooting
@@ -676,6 +705,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();
@@ -837,9 +868,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
return GetPage(query, pageIndex, pageSize, out totalRecords,
- x => MapDtosToContent(x, false,
- // load properties but nothing else
- true, false, false, true),
+ x => MapDtosToContent(x),
filterSql,
ordering);
}
@@ -926,9 +955,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ids.Length > 0)
sql.WhereIn(x => x.UniqueId, ids);
- return _outerRepo.MapDtosToContent(Database.Fetch(sql), false,
- // load everything
- true, true, true, true);
+ return _outerRepo.MapDtosToContent(Database.Fetch(sql));
}
protected override IEnumerable PerformGetByQuery(IQuery query)
@@ -986,9 +1013,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
AddGetByQueryOrderBy(sql);
- return MapDtosToContent(Database.Fetch(sql),
- // load the bare minimum
- false, false, false, true, true);
+ return MapDtosToContent(Database.Fetch(sql));
}
///
@@ -1004,9 +1029,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
AddGetByQueryOrderBy(sql);
- return MapDtosToContent(Database.Fetch(sql),
- // load the bare minimum
- false, false, false, true, true);
+ return MapDtosToContent(Database.Fetch(sql));
}
#endregion
@@ -1070,11 +1093,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
private IEnumerable MapDtosToContent(List dtos,
- bool withCache,
- bool loadProperties,
- bool loadTemplates,
- bool loadSchedule,
- bool loadVariants)
+ bool withCache = false,
+ bool loadProperties = true,
+ bool loadTemplates = true,
+ bool loadSchedule = true,
+ bool loadVariants = true)
{
var temps = new List>();
var contentTypes = new Dictionary();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
index 161db543ba..0eafebbfde 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs
@@ -35,21 +35,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();
@@ -70,35 +82,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;
}
@@ -107,7 +97,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);
}
@@ -116,7 +106,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]));
}
@@ -127,7 +117,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;
}
@@ -146,7 +136,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)
@@ -178,7 +168,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()
@@ -189,7 +179,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;
}
@@ -233,7 +223,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)
@@ -242,7 +232,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();
@@ -356,14 +346,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)
@@ -401,15 +391,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)
@@ -433,10 +423,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
@@ -510,8 +504,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);
@@ -524,9 +529,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; }
@@ -534,11 +547,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
{
}
@@ -589,13 +608,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
@@ -650,7 +669,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 f4fb69258c..081efcfdf6 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs
@@ -12,6 +12,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.NPocoSqlExtensions.Statics;
@@ -27,8 +28,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));
@@ -287,6 +289,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// set tags
SetEntityTags(entity, _tagRepository);
+ PersistRelations(entity);
+
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -343,6 +347,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 892122dff9..42e7d1c32f 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.NPocoSqlExtensions.Statics;
@@ -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));
@@ -321,6 +324,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
SetEntityTags(entity, _tagRepository);
+ PersistRelations(entity);
+
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
entity.ResetDirtyProperties();
@@ -386,6 +391,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..56a6336f75 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/RelationRepository.cs
@@ -6,10 +6,14 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
+using Umbraco.Core.Persistence.DatabaseModelDefinitions;
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.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -19,11 +23,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 +45,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 +72,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 +93,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 +136,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 +150,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 dbb2fc467e..7dc260e4c7 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(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/DataValueReferenceFactoryCollection.cs b/src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs
new file mode 100644
index 0000000000..386ab6a8f3
--- /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(PropertyCollection 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/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs
index cb68531cc7..a02fa71ec7 100644
--- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs
@@ -7,6 +7,7 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.PropertyEditors
{
+
///
/// Represents an editor for editing data values.
///
@@ -63,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...
+
+ ///
+ /// Used for serializing an item for packaging
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
IEnumerable ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published);
+
+ ///
+ /// Used for serializing an item for packaging
+ ///
+ ///
+ ///
+ ///
+ ///
XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService);
+
string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService);
}
}
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/PropertyEditors/PropertyEditorCollection.cs b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs
index 712a66e55d..21854b63c1 100644
--- a/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs
+++ b/src/Umbraco.Core/PropertyEditors/PropertyEditorCollection.cs
@@ -4,6 +4,8 @@ using Umbraco.Core.Manifest;
namespace Umbraco.Core.PropertyEditors
{
+
+
public class PropertyEditorCollection : BuilderCollectionBase
{
public PropertyEditorCollection(DataEditorCollection dataEditors, ManifestParser manifestParser)
@@ -27,4 +29,4 @@ namespace Umbraco.Core.PropertyEditors
return editor != null;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
index 1f004846d0..d95ada499b 100644
--- a/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Core/Runtime/CoreInitialComposer.cs
@@ -43,7 +43,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
@@ -70,11 +70,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 =>
{
@@ -101,13 +105,13 @@ namespace Umbraco.Core.Runtime
factory.GetInstance(),
true, new DatabaseServerMessengerOptions()));
- 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();
@@ -115,7 +119,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/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
index 51e5d756eb..6ed3c85e91 100644
--- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
@@ -39,6 +39,11 @@ namespace Umbraco.Core.Services
int Count();
+ ///
+ /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
+ ///
+ bool HasContentNodes(int id);
+
IEnumerable GetAll(params int[] ids);
IEnumerable GetAll(IEnumerable ids);
diff --git a/src/Umbraco.Core/Services/IRelationService.cs b/src/Umbraco.Core/Services/IRelationService.cs
index ef22632d6e..bf8bcd5b2a 100644
--- a/src/Umbraco.Core/Services/IRelationService.cs
+++ b/src/Umbraco.Core/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.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 7ae330f8f1..da532e2765 100644
--- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -372,6 +372,15 @@ namespace Umbraco.Core.Services.Implement
}
}
+ public bool HasContentNodes(int id)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ scope.ReadLock(ReadLockIds);
+ return Repository.HasContentNodes(id);
+ }
+ }
+
#endregion
#region Save
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