Merge remote-tracking branch 'origin/v8/dev' into v8/feature/AB4550-segments-ui

This commit is contained in:
Niels Lyngsø
2020-01-23 15:04:22 +01:00
147 changed files with 5283 additions and 2292 deletions
@@ -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);
}
@@ -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)
+3
View File
@@ -154,6 +154,9 @@ namespace Umbraco.Core.Composing
public static DataEditorCollection DataEditors
=> Factory.GetInstance<DataEditorCollection>();
public static DataValueReferenceFactoryCollection DataValueReferenceFactories
=> Factory.GetInstance<DataValueReferenceFactoryCollection>();
public static PropertyEditorCollection PropertyEditors
=> Factory.GetInstance<PropertyEditorCollection>();
@@ -49,6 +49,13 @@ namespace Umbraco.Core
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
=> composition.WithCollectionBuilder<DataEditorCollectionBuilder>();
/// <summary>
/// Gets the data value reference factory collection builder.
/// </summary>
/// <param name="composition">The composition.</param>
public static DataValueReferenceFactoryCollectionBuilder DataValueReferenceFactories(this Composition composition)
=> composition.WithCollectionBuilder<DataValueReferenceFactoryCollectionBuilder>();
/// <summary>
/// Gets the property value converters collection builder.
/// </summary>
@@ -9,6 +9,8 @@ namespace Umbraco.Core
/// </summary>
public static class AppSettings
{
public const string MainDomLock = "Umbraco.Core.MainDom.Lock";
// TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27
[Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")]
public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML
+37 -6
View File
@@ -315,34 +315,65 @@ namespace Umbraco.Core
public static class RelationTypes
{
/// <summary>
/// ContentType name for default relation type "Relate Document On Copy".
/// Name for default relation type "Related Media".
/// </summary>
public const string RelatedMediaName = "Related Media";
/// <summary>
/// Alias for default relation type "Related Media"
/// </summary>
public const string RelatedMediaAlias = "umbMedia";
/// <summary>
/// Name for default relation type "Related Document".
/// </summary>
public const string RelatedDocumentName = "Related Document";
/// <summary>
/// Alias for default relation type "Related Document"
/// </summary>
public const string RelatedDocumentAlias = "umbDocument";
/// <summary>
/// Name for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyName = "Relate Document On Copy";
/// <summary>
/// ContentType alias for default relation type "Relate Document On Copy".
/// Alias for default relation type "Relate Document On Copy".
/// </summary>
public const string RelateDocumentOnCopyAlias = "relateDocumentOnCopy";
/// <summary>
/// ContentType name for default relation type "Relate Parent Document On Delete".
/// Name for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteName = "Relate Parent Document On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Document On Delete".
/// Alias for default relation type "Relate Parent Document On Delete".
/// </summary>
public const string RelateParentDocumentOnDeleteAlias = "relateParentDocumentOnDelete";
/// <summary>
/// ContentType name for default relation type "Relate Parent Media Folder On Delete".
/// Name for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteName = "Relate Parent Media Folder On Delete";
/// <summary>
/// ContentType alias for default relation type "Relate Parent Media Folder On Delete".
/// Alias for default relation type "Relate Parent Media Folder On Delete".
/// </summary>
public const string RelateParentMediaFolderOnDeleteAlias = "relateParentMediaFolderOnDelete";
/// <summary>
/// Returns the types of relations that are automatically tracked
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
}
}
}
+226 -87
View File
@@ -12,126 +12,260 @@ namespace Umbraco.Core
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type is invariant.
/// </returns>
public static bool VariesByNothing(this ISimpleContentType contentType) => contentType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type is invariant.
/// </returns>
public static bool VariesByNothing(this IContentTypeBase contentType) => contentType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the property type is invariant.
/// </summary>
public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type is invariant.
/// </returns>
public static bool VariesByNothing(this IPublishedContentType contentType) => contentType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the content type varies by culture.
/// Determines whether the property type is invariant.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type is invariant.
/// </returns>
public static bool VariesByNothing(this PropertyType propertyType) => propertyType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the property type is invariant.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type is invariant.
/// </returns>
public static bool VariesByNothing(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByNothing();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether a variation is invariant.
/// </summary>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation is invariant.
/// </returns>
public static bool VariesByNothing(this ContentVariation variation) => variation == ContentVariation.Nothing;
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture.
/// </returns>
public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture.
/// </returns>
public static bool VariesByCulture(this IContentTypeBase contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by culture.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture.
/// </returns>
public static bool VariesByCulture(this IPublishedContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture.
/// </returns>
public static bool VariesByCulture(this PropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the property type varies by culture.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture.
/// </returns>
public static bool VariesByCulture(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCulture();
/// <summary>
/// Determines whether a variation varies by culture.
/// </summary>
/// <remarks>And then it could also vary by segment.</remarks>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation varies by culture.
/// </returns>
public static bool VariesByCulture(this ContentVariation variation) => (variation & ContentVariation.Culture) > 0;
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by segment.
/// </returns>
public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by segment.
/// </returns>
public static bool VariesBySegment(this IContentTypeBase contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by segment.
/// </returns>
public static bool VariesBySegment(this IPublishedContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by segment.
/// </returns>
public static bool VariesBySegment(this PropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the property type varies by segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by segment.
/// </returns>
public static bool VariesBySegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesBySegment();
/// <summary>
/// Determines whether a variation varies by segment.
/// </summary>
/// <remarks>And then it could also vary by culture.</remarks>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation varies by segment.
/// </returns>
public static bool VariesBySegment(this ContentVariation variation) => (variation & ContentVariation.Segment) > 0;
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this ISimpleContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this IContentTypeBase contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the content type varies by culture and segment.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <returns>
/// A value indicating whether the content type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this IPublishedContentType contentType) => contentType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this PropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether the property type varies by culture and segment.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <returns>
/// A value indicating whether the property type varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this IPublishedPropertyType propertyType) => propertyType.Variations.VariesByCultureAndSegment();
/// <summary>
/// Determines whether a variation varies by culture and segment.
/// </summary>
/// <param name="variation">The variation.</param>
/// <returns>
/// A value indicating whether the variation varies by culture and segment.
/// </returns>
public static bool VariesByCultureAndSegment(this ContentVariation variation) => (variation & ContentVariation.CultureAndSegment) == ContentVariation.CultureAndSegment;
/// <summary>
/// Sets or removes the content type variation depending on the specified value.
/// </summary>
/// <param name="contentType">The content type.</param>
/// <param name="variation">The variation to set or remove.</param>
/// <param name="value">If set to <c>true</c> sets the variation; otherwise, removes the variation.</param>
/// <remarks>
/// This method does not support setting the variation to nothing.
/// </remarks>
public static void SetVariesBy(this IContentTypeBase contentType, ContentVariation variation, bool value = true) => contentType.Variations = contentType.Variations.SetFlag(variation, value);
/// <summary>
/// Sets or removes the property type variation depending on the specified value.
/// </summary>
/// <param name="propertyType">The property type.</param>
/// <param name="variation">The variation to set or remove.</param>
/// <param name="value">If set to <c>true</c> sets the variation; otherwise, removes the variation.</param>
/// <remarks>
/// This method does not support setting the variation to nothing.
/// </remarks>
public static void SetVariesBy(this PropertyType propertyType, ContentVariation variation, bool value = true) => propertyType.Variations = propertyType.Variations.SetFlag(variation, value);
/// <summary>
/// Returns the variations with the variation set or removed depending on the specified value.
/// </summary>
/// <param name="variations">The existing variations.</param>
/// <param name="variation">The variation to set or remove.</param>
/// <param name="value">If set to <c>true</c> sets the variation; otherwise, removes the variation.</param>
/// <returns>
/// The variations with the variation set or removed.
/// </returns>
/// <remarks>
/// This method does not support setting the variation to nothing.
/// </remarks>
public static ContentVariation SetFlag(this ContentVariation variations, ContentVariation variation, bool value = true)
{
return value
? variations | variation // Set flag using bitwise logical OR
: variations & ~variation; // Remove flag using bitwise logical AND with bitwise complement (reversing the bit)
}
/// <summary>
/// Validates that a combination of culture and segment is valid for the variation.
/// </summary>
@@ -140,16 +274,18 @@ namespace Umbraco.Core
/// <param name="segment">The segment.</param>
/// <param name="exact">A value indicating whether to perform exact validation.</param>
/// <param name="wildcards">A value indicating whether to support wildcards.</param>
/// <param name="throwIfInvalid">A value indicating whether to throw a <see cref="NotSupportedException"/> when the combination is invalid.</param>
/// <returns>True if the combination is valid; otherwise false.</returns>
/// <param name="throwIfInvalid">A value indicating whether to throw a <see cref="NotSupportedException" /> when the combination is invalid.</param>
/// <returns>
/// <c>true</c> if the combination is valid; otherwise <c>false</c>.
/// </returns>
/// <exception cref="NotSupportedException">Occurs when the combination is invalid, and <paramref name="throwIfInvalid" /> is true.</exception>
/// <remarks>
/// <para>When validation is exact, the combination must match the variation exactly. For instance, if the variation is Culture, then
/// a culture is required. When validation is not strict, the combination must be equivalent, or more restrictive: if the variation is
/// Culture, an invariant combination is ok.</para>
/// <para>Basically, exact is for one content type, or one property type, and !exact is for "all property types" of one content type.</para>
/// <para>Both <paramref name="culture"/> and <paramref name="segment"/> can be "*" to indicate "all of them".</para>
/// <para>Both <paramref name="culture" /> and <paramref name="segment" /> can be "*" to indicate "all of them".</para>
/// </remarks>
/// <exception cref="NotSupportedException">Occurs when the combination is invalid, and <paramref name="throwIfInvalid"/> is true.</exception>
public static bool ValidateVariation(this ContentVariation variation, string culture, string segment, bool exact, bool wildcards, bool throwIfInvalid)
{
culture = culture.NullOrWhiteSpaceAsNull();
@@ -166,13 +302,14 @@ namespace Umbraco.Core
if (variation.VariesByCulture())
{
// varies by culture
// in exact mode, the culture cannot be null
// in exact mode, the culture cannot be null
if (exact && culture == null)
{
if (throwIfInvalid)
throw new NotSupportedException($"Culture may not be null because culture variation is enabled.");
return false;
}
}
}
else
{
@@ -183,9 +320,10 @@ namespace Umbraco.Core
{
if (throwIfInvalid)
throw new NotSupportedException($"Culture \"{culture}\" is invalid because culture variation is disabled.");
return false;
}
}
}
// if it does not vary by segment
// the segment cannot have a value
@@ -195,6 +333,7 @@ namespace Umbraco.Core
{
if (throwIfInvalid)
throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled.");
return false;
}
+22 -61
View File
@@ -3,81 +3,42 @@
namespace Umbraco.Core
{
/// <summary>
/// Provides extension methods to enums.
/// Provides extension methods to <see cref="Enum"/>.
/// </summary>
public static class EnumExtensions
{
// note:
// - no need to HasFlagExact, that's basically an == test
// - HasFlagAll cannot be named HasFlag because ext. methods never take priority over instance methods
/// <summary>
/// Determines whether a flag enum has all the specified values.
/// Determines whether all the flags/bits are set within the enum value.
/// </summary>
/// <remarks>
/// <para>True when all bits set in <paramref name="uses"/> are set in <paramref name="use"/>, though other bits may be set too.</para>
/// <para>This is the behavior of the original <see cref="Enum.HasFlag"/> method.</para>
/// </remarks>
public static bool HasFlagAll<T>(this T use, T uses)
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="value">The enum value.</param>
/// <param name="flags">The flags.</param>
/// <returns>
/// <c>true</c> if all the flags/bits are set within the enum value; otherwise, <c>false</c>.
/// </returns>
[Obsolete("Use Enum.HasFlag() or bitwise operations (if performance is important) instead.")]
public static bool HasFlagAll<T>(this T value, T flags)
where T : Enum
{
var num = Convert.ToUInt64(use);
var nums = Convert.ToUInt64(uses);
return (num & nums) == nums;
return value.HasFlag(flags);
}
/// <summary>
/// Determines whether a flag enum has any of the specified values.
/// Determines whether any of the flags/bits are set within the enum value.
/// </summary>
/// <remarks>
/// <para>True when at least one of the bits set in <paramref name="uses"/> is set in <paramref name="use"/>.</para>
/// </remarks>
public static bool HasFlagAny<T>(this T use, T uses)
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="value">The value.</param>
/// <param name="flags">The flags.</param>
/// <returns>
/// <c>true</c> if any of the flags/bits are set within the enum value; otherwise, <c>false</c>.
/// </returns>
public static bool HasFlagAny<T>(this T value, T flags)
where T : Enum
{
var num = Convert.ToUInt64(use);
var nums = Convert.ToUInt64(uses);
var v = Convert.ToUInt64(value);
var f = Convert.ToUInt64(flags);
return (num & nums) > 0;
}
/// <summary>
/// Sets a flag of the given input enum
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input">Enum to set flag of</param>
/// <param name="flag">Flag to set</param>
/// <returns>A new enum with the flag set</returns>
public static T SetFlag<T>(this T input, T flag)
where T : Enum
{
var i = Convert.ToUInt64(input);
var f = Convert.ToUInt64(flag);
// bitwise OR to set flag f of enum i
var result = i | f;
return (T)Enum.ToObject(typeof(T), result);
}
/// <summary>
/// Unsets a flag of the given input enum
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="input">Enum to unset flag of</param>
/// <param name="flag">Flag to unset</param>
/// <returns>A new enum with the flag unset</returns>
public static T UnsetFlag<T>(this T input, T flag)
where T : Enum
{
var i = Convert.ToUInt64(input);
var f = Convert.ToUInt64(flag);
// bitwise AND combined with bitwise complement to unset flag f of enum i
var result = i & ~f;
return (T)Enum.ToObject(typeof(T), result);
return (v & f) > 0;
}
}
}
@@ -151,6 +151,8 @@ namespace Umbraco.Core.Migrations.Install
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.KeyValues, Name = "KeyValues" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" });
_database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" });
}
private void CreateContentTypeData()
@@ -310,14 +312,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()
@@ -183,9 +183,12 @@ namespace Umbraco.Core.Migrations.Upgrade
To<RenameUserLoginDtoDateIndex>("{0372A42B-DECF-498D-B4D1-6379E907EB94}");
To<FixContentNuCascade>("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}");
// to 8.6.0
// to 8.6.0...
To<UpdateRelationTypeTable>("{4759A294-9860-46BC-99F9-B4C975CAE580}");
To<AddNewRelationTypes>("{0BC866BC-0665-487A-9913-0290BD0169AD}");
To<AddPropertyTypeValidationMessageColumns>("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}");
To<MissingContentVersionsIndexes>("{EE288A91-531B-4995-8179-1D62D9AA3E2E}");
To<AddMainDomLock>("{2AB29964-02A1-474D-BD6B-72148D2A53A2}");
//FINAL
}
@@ -0,0 +1,16 @@
using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class AddMainDomLock : MigrationBase
{
public AddMainDomLock(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
Database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" });
}
}
}
@@ -0,0 +1,33 @@
using Umbraco.Core.Migrations.Install;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
/// <summary>
/// Ensures the new relation types are created
/// </summary>
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();
}
}
}
@@ -3,6 +3,7 @@ using Umbraco.Core.Persistence.Dtos;
namespace Umbraco.Core.Migrations.Upgrade.V_8_6_0
{
public class AddPropertyTypeValidationMessageColumns : MigrationBase
{
public AddPropertyTypeValidationMessageColumns(IMigrationContext context)
@@ -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();
}
}
}
@@ -1,5 +1,6 @@
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Represents an uploaded file for a property.
/// </summary>
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Editors
{
/// <summary>
/// Used to track reference to other entities in a property value
/// </summary>
public struct UmbracoEntityReference : IEquatable<UmbracoEntityReference>
{
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<Udi>.Default.Equals(Udi, other.Udi) &&
RelationTypeAlias == other.RelationTypeAlias;
}
public override int GetHashCode()
{
var hashCode = -487348478;
hashCode = hashCode * -1521134295 + EqualityComparer<Udi>.Default.GetHashCode(Udi);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.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);
}
}
}
@@ -24,7 +24,7 @@
/// Sets the parent entity.
/// </summary>
/// <remarks>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 <see cref="ParentId"/> getter.</remarks>
void SetParent(ITreeEntity parent);
@@ -53,4 +53,4 @@
/// </remarks>
bool Trashed { get; }
}
}
}
@@ -1,13 +1,6 @@
namespace Umbraco.Core.Models.Entities
{
public class MemberEntitySlim : EntitySlim, IMemberEntitySlim
public class MemberEntitySlim : ContentEntitySlim, IMemberEntitySlim
{
public string ContentTypeAlias { get; set; }
/// <inheritdoc />
public string ContentTypeIcon { get; set; }
/// <inheritdoc />
public string ContentTypeThumbnail { get; set; }
}
}
}
+8 -1
View File
@@ -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; }
/// <summary>
/// Gets or sets the Child Id of the Relation (Destination)
/// </summary>
[DataMember]
int ChildId { get; set; }
[DataMember]
Guid ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the <see cref="RelationType"/> for the Relation
/// </summary>
+2 -2
View File
@@ -29,13 +29,13 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
Guid ParentObjectType { get; set; }
Guid? ParentObjectType { get; set; }
/// <summary>
/// Gets or sets the Childs object type id
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
Guid ChildObjectType { get; set; }
Guid? ChildObjectType { get; set; }
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ namespace Umbraco.Core.Models
/// </summary>
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
+30 -1
View File
@@ -17,13 +17,36 @@ namespace Umbraco.Core.Models
private IRelationType _relationType;
private string _comment;
/// <summary>
/// Constructor for constructing the entity to be created
/// </summary>
/// <param name="parentId"></param>
/// <param name="childId"></param>
/// <param name="relationType"></param>
public Relation(int parentId, int childId, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
}
/// <summary>
/// Constructor for reconstructing the entity from the data source
/// </summary>
/// <param name="parentId"></param>
/// <param name="childId"></param>
/// <param name="parentObjectType"></param>
/// <param name="childObjectType"></param>
/// <param name="relationType"></param>
public Relation(int parentId, int childId, Guid parentObjectType, Guid childObjectType, IRelationType relationType)
{
_parentId = parentId;
_childId = childId;
_relationType = relationType;
ParentObjectType = parentObjectType;
ChildObjectType = childObjectType;
}
/// <summary>
/// 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; }
/// <summary>
/// Gets or sets the Child Id of the Relation (Destination)
/// </summary>
@@ -45,6 +71,9 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _childId, nameof(ChildId));
}
[DataMember]
public Guid ChildObjectType { get; set; }
/// <summary>
/// Gets or sets the <see cref="RelationType"/> for the Relation
/// </summary>
+15 -17
View File
@@ -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;
}
/// <summary>
/// Gets or sets the Name of the RelationType
@@ -72,7 +70,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
public Guid ParentObjectType
public Guid? ParentObjectType
{
get => _parentObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _parentObjectType, nameof(ParentObjectType));
@@ -83,7 +81,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <remarks>Corresponds to the NodeObjectType in the umbracoNode table</remarks>
[DataMember]
public Guid ChildObjectType
public Guid? ChildObjectType
{
get => _childObjectType;
set => SetPropertyValueAndDetectChanges(value, ref _childObjectType, nameof(ChildObjectType));
@@ -8,6 +8,11 @@ namespace Umbraco.Core
/// </summary>
public static class Locks
{
/// <summary>
/// The <see cref="IMainDom"/> lock
/// </summary>
public const int MainDom = -1000;
/// <summary>
/// All servers.
/// </summary>
@@ -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; }
}
}
@@ -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; }
@@ -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<Relation,RelationDto>
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
}
}
@@ -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);
@@ -14,7 +14,21 @@ namespace Umbraco.Core.Persistence
/// </summary>
public static partial class NPocoDatabaseExtensions
{
// TODO: review NPoco native InsertBulk to replace the code below
/// <summary>
/// Configures NPoco's SqlBulkCopyHelper to use the correct SqlConnection and SqlTransaction instances from the underlying RetryDbConnection and ProfiledDbTransaction
/// </summary>
/// <remarks>
/// This is required to use NPoco's own <see cref="Database.InsertBulk{T}(IEnumerable{T})" /> 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.
/// </remarks>
public static void ConfigureNPocoBulkExtensions()
{
SqlBulkCopyHelper.SqlConnectionResolver = dbConn => GetTypedConnection<SqlConnection>(dbConn);
SqlBulkCopyHelper.SqlTransactionResolver = dbTran => GetTypedTransaction<SqlTransaction>(dbTran);
}
/// <summary>
/// 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);
}
@@ -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<IEntitySlim> GetAll(Guid objectType, params int[] ids);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids);
IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys);
/// <summary>
/// Gets entities for a query
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query);
/// <summary>
/// Gets entities for a query and a specific object type allowing the query to be slightly more optimized
/// </summary>
/// <param name="query"></param>
/// <param name="objectType"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType);
UmbracoObjectTypes GetObjectType(int id);
@@ -30,7 +43,41 @@ namespace Umbraco.Core.Persistence.Repositories
bool Exists(int id);
bool Exists(Guid key);
/// <summary>
/// Gets paged entities for a query and a subset of object types
/// </summary>
/// <param name="query"></param>
/// <param name="objectTypes"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="filter"></param>
/// <param name="ordering"></param>
/// <param name="sqlCustomization">
/// A callback providing the ability to customize the generated SQL used to retrieve entities
/// </param>
/// <returns>
/// A collection of mixed entity types which would be of type <see cref="IEntitySlim"/>, <see cref="IDocumentEntitySlim"/>, <see cref="IMediaEntitySlim"/>,
/// <see cref="IMemberEntitySlim"/>
/// </returns>
IEnumerable<IEntitySlim> GetPagedResultsByQuery(
IQuery<IUmbracoEntity> query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering, Action<Sql<ISqlContext>> sqlCustomization = null);
/// <summary>
/// Gets paged entities for a query and a specific object type
/// </summary>
/// <param name="query"></param>
/// <param name="objectType"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="filter"></param>
/// <param name="ordering"></param>
/// <returns></returns>
IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering);
}
}
@@ -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<int, IRelation>
{
IEnumerable<IRelation> GetPagedRelationsByQuery(IQuery<IRelation> query, long pageIndex, int pageSize, out long totalRecords, Ordering ordering);
/// <summary>
/// Persist multiple <see cref="IRelation"/> at once
/// </summary>
/// <param name="relations"></param>
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Deletes all relations for a parent for any specified relation type alias
/// </summary>
/// <param name="parentId"></param>
/// <param name="relationTypeAliases">
/// A list of relation types to match for deletion, if none are specified then all relations for this parent id are deleted
/// </param>
void DeleteByParent(int parentId, params string[] relationTypeAliases);
IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int childId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int parentId, long pageIndex, int pageSize, out long totalRecords, params Guid[] entityTypes);
}
}
@@ -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
{
/// <summary>
///
/// This is used for unit tests ONLY
/// </summary>
public static bool ThrowOnWarning = false;
}
internal abstract class ContentRepositoryBase<TId, TEntity, TRepository> : NPocoRepositoryBase<TId, TEntity>, IContentRepository<TId, TEntity>
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<PropertyEditorCollection> _propertyEditors;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories;
/// <summary>
///
/// </summary>
/// <param name="scopeAccessor"></param>
/// <param name="cache"></param>
/// <param name="logger"></param>
/// <param name="languageRepository"></param>
/// <param name="propertyEditors">
/// 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
/// </param>
protected ContentRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger,
ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> 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<UmbracoEntityReference>();
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<NodeIdKey>(Sql().Select<NodeDto>(x => x.NodeId, x => x.UniqueId).From<NodeDto>().WhereIn<NodeDto>(x => x.UniqueId, udiToGuids.Values))
.ToDictionary(x => x.UniqueId, x => x.NodeId);
var allRelationTypes = RelationTypeRepository.GetMany(Array.Empty<int>())
.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; }
}
}
}
@@ -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
/// </remarks>
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> propertyEditorCollection, DataValueReferenceFactoryCollection dataValueReferenceFactories)
: base(scopeAccessor, appCaches, logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditorCollection, dataValueReferenceFactories)
{
}
@@ -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)
/// <summary>
/// Constructor
/// </summary>
/// <param name="scopeAccessor"></param>
/// <param name="appCaches"></param>
/// <param name="logger"></param>
/// <param name="contentTypeRepository"></param>
/// <param name="templateRepository"></param>
/// <param name="tagRepository"></param>
/// <param name="languageRepository"></param>
/// <param name="propertyEditors">
/// 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
/// </param>
public DocumentRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger,
IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository,
Lazy<PropertyEditorCollection> 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));
@@ -484,6 +500,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ClearEntityTags(entity, _tagRepository);
}
PersistRelations(entity);
entity.ResetDirtyProperties();
// troubleshooting
@@ -687,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();
@@ -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<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> 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<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid[] objectTypes, long pageIndex, int pageSize, out long totalRecords,
IQuery<IUmbracoEntity> filter, Ordering ordering, Action<Sql<ISqlContext>> 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<BaseDto> dtos;
if(isContent)
{
var page = Database.Page<ContentEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMedia)
{
var page = Database.Page<MediaEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMember)
{
var page = Database.Page<MemberEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else
{
var page = Database.Page<BaseDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
var page = Database.Page<GenericContentEntityDto>(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<DocumentEntitySlim>());
BuildVariants(entities.OfType<DocumentEntitySlim>());
return entities;
}
@@ -107,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var sql = GetBaseWhere(false, false, false, false, key);
var dto = Database.FirstOrDefault<BaseDto>(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<ContentEntityDto>(sql);
var cdtos = Database.Fetch<DocumentEntityDto>(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<BaseDto>(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<ContentEntityDto>(sql);
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
return cdtos.Count == 0
? Enumerable.Empty<IEntitySlim>()
@@ -189,7 +179,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
? (IEnumerable<BaseDto>)Database.Fetch<MediaEntityDto>(sql)
: Database.Fetch<BaseDto>(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<BaseDto>(sql);
return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
return dtos.Select(BuildEntity).ToList();
}
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> 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<IUmbracoEntity>(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<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action<Sql<ISqlContext>> 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<ISqlContext> GetBase(bool isContent, bool isMedia, bool isMember, Action<Sql<ISqlContext>> 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<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.InnerJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
.LeftJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
.LeftJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.LeftJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>((left, right) => left.ContentTypeId == right.NodeId);
}
if (isContent)
{
sql
.InnerJoin<DocumentDto>().On<NodeDto, DocumentDto>((left, right) => left.NodeId == right.NodeId);
.LeftJoin<DocumentDto>().On<NodeDto, DocumentDto>((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<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid[] objectTypes)
{
return GetBase(isContent, isMedia, isMember, filter, isCount)
.Where<NodeDto>(x => x.NodeObjectType == objectType);
var sql = GetBase(isContent, isMedia, isMember, filter, isCount);
if (objectTypes.Length > 0)
{
sql.WhereIn<NodeDto>(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
/// <summary>
/// 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
/// </summary>
private class ContentEntityDto : BaseDto
private class GenericContentEntityDto : DocumentEntityDto
{
public string MediaPath { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a document item with its variation info
/// </summary>
private class DocumentEntityDto : BaseDto
{
public ContentVariation Variations { get; set; }
@@ -534,11 +547,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public bool Edited { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a media item with its media path info
/// </summary>
private class MediaEntityDto : BaseDto
{
public string MediaPath { get; set; }
}
/// <summary>
/// The DTO used to fetch results for a member item
/// </summary>
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;
@@ -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> 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();
@@ -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<PropertyEditorCollection> 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();
@@ -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<int, IRelation>, 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<int,Relation>
@@ -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<IRelation> PerformGetAll(params int[] ids)
@@ -67,26 +72,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable<IRelation> DtosToEntities(IEnumerable<RelationDto> 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<ISqlContext> GetBaseQuery(bool isCount)
{
var sql = Sql();
if (isCount)
{
return Sql().SelectCount().From<RelationDto>();
}
sql = isCount
? sql.SelectCount()
: sql.Select<RelationDto>();
var sql = Sql().Select<RelationDto>()
.AndSelect<NodeDto>("uchild", x => Alias(x.NodeObjectType, "childObjectType"))
.AndSelect<NodeDto>("uparent", x => Alias(x.NodeObjectType, "parentObjectType"))
.From<RelationDto>()
.InnerJoin<NodeDto>("uchild").On<RelationDto, NodeDto>((rel, node) => rel.ChildId == node.NodeId, aliasRight: "uchild")
.InnerJoin<NodeDto>("uparent").On<RelationDto, NodeDto>((rel, node) => rel.ParentId == node.NodeId, aliasRight: "uparent");
sql
.From<RelationDto>();
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
/// <summary>
/// Used for joining the entity query with relations for the paging methods
/// </summary>
/// <param name="sql"></param>
private void SqlJoinRelations(Sql<ISqlContext> 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<RelationDto>().On<NodeDto, RelationDto>((left, right) => left.NodeId == right.ChildId || left.NodeId == right.ParentId);
sql.LeftJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>((left, right) => left.RelationType == right.Id);
}
public IEnumerable<IUmbracoEntity> 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<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.Where<RelationDto>(rel => rel.ChildId == childId);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ParentId == childId || node.NodeId != childId);
});
}
public IEnumerable<IUmbracoEntity> 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<IUmbracoEntity>(), entityTypes, pageIndex, pageSize, out totalRecords, null, null, sql =>
{
SqlJoinRelations(sql);
sql.Where<RelationDto>(rel => rel.ParentId == parentId);
sql.Where<RelationDto, NodeDto>((rel, node) => rel.ChildId == parentId || node.NodeId != parentId);
});
}
public void Save(IEnumerable<IRelation> 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<IRelation> GetPagedRelationsByQuery(IQuery<IRelation> 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<IRelation>(sql, query);
sql = translator.Translate();
// apply ordering
ApplyOrdering(ref sql, ordering);
var pageIndexToFetch = pageIndex + 1;
var page = Database.Page<RelationDto>(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<RelationDto>(x => x.Id)
.From<RelationDto>()
.InnerJoin<RelationTypeDto>().On<RelationDto, RelationTypeDto>(x => x.RelationType, x => x.Id)
.Where<RelationDto>(x => x.ParentId == parentId);
if (relationTypeAliases.Length > 0)
{
subQuery.WhereIn<RelationTypeDto>(x => x.Alias, relationTypeAliases);
}
Database.Execute(Sql().Delete<RelationDto>().WhereIn<RelationDto>(x => x.Id, subQuery));
}
/// <summary>
/// Used to populate the object types after insert/update
/// </summary>
/// <param name="entities"></param>
private void PopulateObjectTypes(params IRelation[] entities)
{
var entityIds = entities.Select(x => x.ParentId).Concat(entities.Select(y => y.ChildId)).Distinct();
var nodes = Database.Fetch<NodeDto>(Sql().Select<NodeDto>().From<NodeDto>()
.WhereIn<NodeDto>(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<ISqlContext> 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);
}
}
}
@@ -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;
}
}
}
@@ -250,6 +250,11 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
}
public override void WriteLock(IDatabase db, params int[] lockIds)
{
WriteLock(db, TimeSpan.FromMilliseconds(1800), lockIds);
}
public void WriteLock(IDatabase db, TimeSpan timeout, params int[] lockIds)
{
// soon as we get Database, a transaction is started
@@ -260,7 +265,7 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
db.Execute(@"SET LOCK_TIMEOUT 1800;");
db.Execute($"SET LOCK_TIMEOUT {timeout.TotalMilliseconds};");
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
@@ -35,6 +35,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// </remarks>
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<T> + sub query, see RelationRepository.DeleteByParent for example
return
new Sql(string.Format(
@@ -44,6 +44,8 @@ namespace Umbraco.Core.Persistence
_commandRetryPolicy = commandRetryPolicy;
EnableSqlTrace = EnableSqlTraceDefault;
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
/// <summary>
@@ -57,6 +59,8 @@ namespace Umbraco.Core.Persistence
_logger = logger;
EnableSqlTrace = EnableSqlTraceDefault;
NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions();
}
#endregion
@@ -19,6 +19,7 @@ namespace Umbraco.Core.PropertyEditors
public class DataEditor : IDataEditor
{
private IDictionary<string, object> _defaultConfiguration;
private IDataValueEditor _dataValueEditor;
/// <summary>
/// Initializes a new instance of the <see cref="DataEditor"/> class.
@@ -90,7 +91,7 @@ namespace Umbraco.Core.PropertyEditors
/// simple enough for now.</para>
/// </remarks>
// TODO: point of that one? shouldn't we always configure?
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_dataValueEditor ?? (_dataValueEditor = CreateValueEditor()));
/// <inheritdoc />
/// <remarks>
@@ -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;
}
@@ -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<IDataValueReferenceFactory>
{
public DataValueReferenceFactoryCollection(IEnumerable<IDataValueReferenceFactory> items)
: base(items)
{ }
public IEnumerable<UmbracoEntityReference> GetAllReferences(PropertyCollection properties, PropertyEditorCollection propertyEditors)
{
var trackedRelations = new List<UmbracoEntityReference>();
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;
}
}
}
@@ -0,0 +1,9 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.PropertyEditors
{
public class DataValueReferenceFactoryCollectionBuilder : OrderedCollectionBuilderBase<DataValueReferenceFactoryCollectionBuilder, DataValueReferenceFactoryCollection, IDataValueReferenceFactory>
{
protected override DataValueReferenceFactoryCollectionBuilder This => this;
}
}
@@ -7,6 +7,7 @@ using Umbraco.Core.Services;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Represents an editor for editing data values.
/// </summary>
@@ -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...
/// <summary>
/// Used for serializing an <see cref="IContent"/> item for packaging
/// </summary>
/// <param name="property"></param>
/// <param name="dataTypeService"></param>
/// <param name="localizationService"></param>
/// <param name="published"></param>
/// <returns></returns>
IEnumerable<XElement> ConvertDbToXml(Property property, IDataTypeService dataTypeService, ILocalizationService localizationService, bool published);
/// <summary>
/// Used for serializing an <see cref="IContent"/> item for packaging
/// </summary>
/// <param name="propertyType"></param>
/// <param name="value"></param>
/// <param name="dataTypeService"></param>
/// <returns></returns>
XNode ConvertDbToXml(PropertyType propertyType, object value, IDataTypeService dataTypeService);
string ConvertDbToString(PropertyType propertyType, object value, IDataTypeService dataTypeService);
}
}
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using Umbraco.Core.Models.Editors;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Resolve references from <see cref="IDataValueEditor"/> values
/// </summary>
public interface IDataValueReference
{
/// <summary>
/// Returns any references contained in the value
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
IEnumerable<UmbracoEntityReference> GetReferences(object value);
}
}
@@ -0,0 +1,18 @@
namespace Umbraco.Core.PropertyEditors
{
public interface IDataValueReferenceFactory
{
/// <summary>
/// Gets a value indicating whether the DataValueReference lookup supports a datatype (data editor).
/// </summary>
/// <param name="dataType">The datatype.</param>
/// <returns>A value indicating whether the converter supports a datatype.</returns>
bool IsForEditor(IDataEditor dataEditor);
/// <summary>
///
/// </summary>
/// <returns></returns>
IDataValueReference GetDataValueReference();
}
}
@@ -4,6 +4,8 @@ using Umbraco.Core.Manifest;
namespace Umbraco.Core.PropertyEditors
{
public class PropertyEditorCollection : BuilderCollectionBase<IDataEditor>
{
public PropertyEditorCollection(DataEditorCollection dataEditors, ManifestParser manifestParser)
@@ -27,4 +29,4 @@ namespace Umbraco.Core.PropertyEditors
return editor != null;
}
}
}
}
@@ -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<MapperCollectionBuilder>().AddCoreMappers();
composition.Mappers().AddCoreMappers();
// register the scope provider
composition.RegisterUnique<ScopeProvider>(); // implements both IScopeProvider and IScopeAccessor
@@ -70,11 +70,15 @@ namespace Umbraco.Core.Runtime
composition.ManifestFilters();
// properties and parameters derive from data editors
composition.WithCollectionBuilder<DataEditorCollectionBuilder>()
composition.DataEditors()
.Add(() => composition.TypeLoader.GetDataEditors());
composition.RegisterUnique<PropertyEditorCollection>();
composition.RegisterUnique<ParameterEditorCollection>();
// 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<IServerRegistrar>(f =>
{
@@ -101,13 +105,13 @@ namespace Umbraco.Core.Runtime
factory.GetInstance<IGlobalSettings>(),
true, new DatabaseServerMessengerOptions()));
composition.WithCollectionBuilder<CacheRefresherCollectionBuilder>()
composition.CacheRefreshers()
.Add(() => composition.TypeLoader.GetCacheRefreshers());
composition.WithCollectionBuilder<PackageActionCollectionBuilder>()
composition.PackageActions()
.Add(() => composition.TypeLoader.GetPackageActions());
composition.WithCollectionBuilder<PropertyValueConverterCollectionBuilder>()
composition.PropertyValueConverters()
.Append(composition.TypeLoader.GetTypes<IPropertyValueConverter>());
composition.RegisterUnique<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
@@ -115,7 +119,7 @@ namespace Umbraco.Core.Runtime
composition.RegisterUnique<IShortStringHelper>(factory
=> new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(factory.GetInstance<IUmbracoSettingsSection>())));
composition.WithCollectionBuilder<UrlSegmentProviderCollectionBuilder>()
composition.UrlSegmentProviders()
.Append<DefaultUrlSegmentProvider>();
composition.RegisterUnique<IMigrationBuilder>(factory => new MigrationBuilder(factory));
+1 -1
View File
@@ -147,7 +147,7 @@ namespace Umbraco.Core.Runtime
// TODO: remove this in netcore, this is purely backwards compat hacks with the empty ctor
if (MainDom == null)
{
MainDom = new MainDom(Logger);
MainDom = new MainDom(Logger, new MainDomSemaphoreLock(Logger));
}
@@ -1,5 +1,6 @@
using System;
// TODO: Can't change namespace due to breaking changes, change in netcore
namespace Umbraco.Core
{
/// <summary>
+29
View File
@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
namespace Umbraco.Core.Runtime
{
/// <summary>
/// An application-wide distributed lock
/// </summary>
/// <remarks>
/// Disposing releases the lock
/// </remarks>
public interface IMainDomLock : IDisposable
{
/// <summary>
/// Acquires an application-wide distributed lock
/// </summary>
/// <param name="millisecondsTimeout"></param>
/// <returns>
/// An awaitable boolean value which will be false if the elapsed millsecondsTimeout value is exceeded
/// </returns>
Task<bool> AcquireLockAsync(int millisecondsTimeout);
/// <summary>
/// Wait on a background thread to receive a signal from another AppDomain
/// </summary>
/// <returns></returns>
Task ListenAsync();
}
}
@@ -4,10 +4,13 @@ using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Web.Hosting;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
namespace Umbraco.Core
namespace Umbraco.Core.Runtime
{
/// <summary>
/// Provides the full implementation of <see cref="IMainDom"/>.
/// </summary>
@@ -20,18 +23,11 @@ namespace Umbraco.Core
#region Vars
private readonly ILogger _logger;
private readonly IMainDomLock _mainDomLock;
// our own lock for local consistency
private object _locko = new object();
// async lock representing the main domain lock
private readonly SystemLock _systemLock;
private IDisposable _systemLocker;
// event wait handle used to notify current main domain that it should
// release the lock because a new domain wants to be the main domain
private readonly EventWaitHandle _signal;
private bool _isInitialized;
// indicates whether...
private bool _isMainDom; // we are the main domain
@@ -40,39 +36,19 @@ namespace Umbraco.Core
// actions to run before releasing the main domain
private readonly List<KeyValuePair<int, Action>> _callbacks = new List<KeyValuePair<int, Action>>();
private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
private const int LockTimeoutMilliseconds = 40000; // 40 seconds
#endregion
#region Ctor
// initializes a new instance of MainDom
public MainDom(ILogger logger)
public MainDom(ILogger logger, IMainDomLock systemLock)
{
HostingEnvironment.RegisterObject(this);
_logger = logger;
// HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty;
// combining with the physical path because if running on eg IIS Express,
// two sites could have the same appId even though they are different.
//
// now what could still collide is... two sites, running in two different processes
// and having the same appId, and running on the same app physical path
//
// we *cannot* use the process ID here because when an AppPool restarts it is
// a new process for the same application path
var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty;
var hash = (appId + ":::" + appPath).GenerateHash<SHA1>();
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
_systemLock = new SystemLock(lockName);
var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT";
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
_mainDomLock = systemLock;
}
#endregion
@@ -141,13 +117,14 @@ namespace Umbraco.Core
continue;
}
}
_logger.Debug<MainDom>("Stopped ({SignalSource})", source);
}
finally
{
// in any case...
_isMainDom = false;
_systemLocker?.Dispose();
_mainDomLock.Dispose();
_logger.Info<MainDom>("Released ({SignalSource})", source);
}
@@ -167,36 +144,33 @@ namespace Umbraco.Core
_logger.Info<MainDom>("Acquiring.");
// signal other instances that we want the lock, then wait one the lock,
// which may timeout, and this is accepted - see comments below
// Get the lock
var acquired = _mainDomLock.AcquireLockAsync(LockTimeoutMilliseconds).GetAwaiter().GetResult();
// signal, then wait for the lock, then make sure the event is
// reset (maybe there was noone listening..)
_signal.Set();
if (!acquired)
{
_logger.Info<MainDom>("Cannot acquire (timeout).");
// if more than 1 instance reach that point, one will get the lock
// and the other one will timeout, which is accepted
// In previous versions we'd let a TimeoutException be thrown
// and the appdomain would not start. We have the opportunity to allow it to
// start without having MainDom? This would mean that it couldn't write
// to nucache/examine and would only be ok if this was a super short lived appdomain.
// maybe safer to just keep throwing in this case.
throw new TimeoutException("Cannot acquire MainDom");
// return false;
}
//This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset.
try
{
_systemLocker = _systemLock.Lock(LockTimeoutMilliseconds);
// Listen for the signal from another AppDomain coming online to release the lock
_mainDomLock.ListenAsync().ContinueWith(_ => OnSignal("signal"));
}
finally
catch (OperationCanceledException ex)
{
// we need to reset the event, because otherwise we would end up
// signaling ourselves and committing suicide immediately.
// only 1 instance can reach that point, but other instances may
// have started and be trying to get the lock - they will timeout,
// which is accepted
_signal.Reset();
// the waiting task could be canceled if this appdomain is naturally shutting down, we'll just swallow this exception
_logger.Warn<MainDom>(ex, ex.Message);
}
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
_signal.WaitOneAsync()
.ContinueWith(_ => OnSignal("signal"));
_logger.Info<MainDom>("Acquired.");
return true;
@@ -205,6 +179,10 @@ namespace Umbraco.Core
/// <summary>
/// Gets a value indicating whether the current domain is the main domain.
/// </summary>
/// <remarks>
/// The lazy initializer call will only call the Acquire callback when it's not been initialized, else it will just return
/// the value from _isMainDom which means when we set _isMainDom to false again after being signaled, this will return false;
/// </remarks>
public bool IsMainDom => LazyInitializer.EnsureInitialized(ref _isMainDom, ref _isInitialized, ref _locko, () => Acquire());
// IRegisteredObject
@@ -230,8 +208,7 @@ namespace Umbraco.Core
{
if (disposing)
{
_signal?.Close();
_signal?.Dispose();
_mainDomLock.Dispose();
}
disposedValue = true;
@@ -244,5 +221,25 @@ namespace Umbraco.Core
}
#endregion
public static string GetMainDomId()
{
// HostingEnvironment.ApplicationID is null in unit tests, making ReplaceNonAlphanumericChars fail
var appId = HostingEnvironment.ApplicationID?.ReplaceNonAlphanumericChars(string.Empty) ?? string.Empty;
// combining with the physical path because if running on eg IIS Express,
// two sites could have the same appId even though they are different.
//
// now what could still collide is... two sites, running in two different processes
// and having the same appId, and running on the same app physical path
//
// we *cannot* use the process ID here because when an AppPool restarts it is
// a new process for the same application path
var appPath = HostingEnvironment.ApplicationPhysicalPath?.ToLowerInvariant() ?? string.Empty;
var hash = (appId + ":::" + appPath).GenerateHash<SHA1>();
return hash;
}
}
}
@@ -0,0 +1,95 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Runtime
{
/// <summary>
/// Uses a system-wide Semaphore and EventWaitHandle to synchronize the current AppDomain
/// </summary>
internal class MainDomSemaphoreLock : IMainDomLock
{
private readonly SystemLock _systemLock;
// event wait handle used to notify current main domain that it should
// release the lock because a new domain wants to be the main domain
private readonly EventWaitHandle _signal;
private readonly ILogger _logger;
private IDisposable _lockRelease;
public MainDomSemaphoreLock(ILogger logger)
{
var lockName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-LCK";
_systemLock = new SystemLock(lockName);
var eventName = "UMBRACO-" + MainDom.GetMainDomId() + "-MAINDOM-EVT";
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
_logger = logger;
}
//WaitOneAsync (ext method) will wait for a signal without blocking the main thread, the waiting is done on a background thread
public Task ListenAsync() => _signal.WaitOneAsync();
public Task<bool> AcquireLockAsync(int millisecondsTimeout)
{
// signal other instances that we want the lock, then wait on the lock,
// which may timeout, and this is accepted - see comments below
// signal, then wait for the lock, then make sure the event is
// reset (maybe there was noone listening..)
_signal.Set();
// if more than 1 instance reach that point, one will get the lock
// and the other one will timeout, which is accepted
//This can throw a TimeoutException - in which case should this be in a try/finally to ensure the signal is always reset.
try
{
_lockRelease = _systemLock.Lock(millisecondsTimeout);
return Task.FromResult(true);
}
catch (TimeoutException ex)
{
_logger.Error<MainDomSemaphoreLock>(ex);
return Task.FromResult(false);
}
finally
{
// we need to reset the event, because otherwise we would end up
// signaling ourselves and committing suicide immediately.
// only 1 instance can reach that point, but other instances may
// have started and be trying to get the lock - they will timeout,
// which is accepted
_signal.Reset();
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_lockRelease?.Dispose();
_signal.Close();
_signal.Dispose();
}
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
}
#endregion
}
}
+413
View File
@@ -0,0 +1,413 @@
using System;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Runtime
{
internal class SqlMainDomLock : IMainDomLock
{
private string _lockId;
private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom";
private const string UpdatedSuffix = "_updated";
private readonly ILogger _logger;
private IUmbracoDatabase _db;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider();
private bool _mainDomChanging = false;
private readonly UmbracoDatabaseFactory _dbFactory;
private bool _hasError;
private object _locker = new object();
public SqlMainDomLock(ILogger logger)
{
// unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer
_lockId = Guid.NewGuid().ToString();
_logger = logger;
_dbFactory = new UmbracoDatabaseFactory(
Constants.System.UmbracoConnectionName,
_logger,
new Lazy<IMapperCollection>(() => new Persistence.Mappers.MapperCollection(Enumerable.Empty<BaseMapper>())));
}
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
{
if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider))
throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server");
_sqlServerSyntax = sqlServerSyntaxProvider;
_logger.Debug<SqlMainDomLock>("Acquiring lock...");
var db = GetDatabase();
var tempId = Guid.NewGuid().ToString();
try
{
db.BeginTransaction(IsolationLevel.ReadCommitted);
try
{
// wait to get a write lock
_sqlServerSyntax.WriteLock(db, TimeSpan.FromMilliseconds(millisecondsTimeout), Constants.Locks.MainDom);
}
catch (Exception ex)
{
if (IsLockTimeoutException(ex))
{
_logger.Error<SqlMainDomLock>(ex, "Sql timeout occurred, could not acquire MainDom.");
_hasError = true;
return false;
}
// unexpected (will be caught below)
throw;
}
var result = InsertLockRecord(tempId); //we change the row to a random Id to signal other MainDom to shutdown
if (result == RecordPersistenceType.Insert)
{
// if we've inserted, then there was no MainDom so we can instantly acquire
// TODO: see the other TODO, could we just delete the row and that would indicate that we
// are MainDom? then we don't leave any orphan rows behind.
InsertLockRecord(_lockId); // so update with our appdomain id
_logger.Debug<SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
return true;
}
// if we've updated, this means there is an active MainDom, now we need to wait to
// for the current MainDom to shutdown which also requires releasing our write lock
}
catch (Exception ex)
{
ResetDatabase();
// unexpected
_logger.Error<SqlMainDomLock>(ex, "Unexpected error, cannot acquire MainDom");
_hasError = true;
return false;
}
finally
{
db?.CompleteTransaction();
}
return await WaitForExistingAsync(tempId, millisecondsTimeout);
}
public Task ListenAsync()
{
if (_hasError)
{
_logger.Warn<SqlMainDomLock>("Could not acquire MainDom, listening is canceled.");
return Task.CompletedTask;
}
// Create a long running task (dedicated thread)
// to poll to check if we are still the MainDom registered in the DB
return Task.Factory.StartNew(ListeningLoop, _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
private void ListeningLoop()
{
while (true)
{
// poll every 1 second
Thread.Sleep(1000);
lock (_locker)
{
// If cancellation has been requested we will just exit. Depending on timing of the shutdown,
// we will have already flagged _mainDomChanging = true, or we're shutting down faster than
// the other MainDom is taking to startup. In this case the db row will just be deleted and the
// new MainDom will just take over.
if (_cancellationTokenSource.IsCancellationRequested)
return;
var db = GetDatabase();
try
{
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a read lock
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);
// TODO: We could in theory just check if the main dom row doesn't exist, that could indicate that
// we are still the maindom. An empty value might be better because then we won't have any orphan rows
// if the app is terminated. Could that work?
if (!IsMainDomValue(_lockId))
{
// we are no longer main dom, another one has come online, exit
_mainDomChanging = true;
_logger.Debug<SqlMainDomLock>("Detected new booting application, releasing MainDom lock.");
return;
}
}
catch (Exception ex)
{
ResetDatabase();
// unexpected
_logger.Error<SqlMainDomLock>(ex, "Unexpected error, listening is canceled.");
_hasError = true;
return;
}
finally
{
db?.CompleteTransaction();
}
}
}
}
private void ResetDatabase()
{
if (_db.InTransaction)
_db.AbortTransaction();
_db.Dispose();
_db = null;
}
private IUmbracoDatabase GetDatabase()
{
if (_db != null)
return _db;
_db = _dbFactory.CreateDatabase();
return _db;
}
/// <summary>
/// Wait for any existing MainDom to release so we can continue booting
/// </summary>
/// <param name="tempId"></param>
/// <param name="millisecondsTimeout"></param>
/// <returns></returns>
private Task<bool> WaitForExistingAsync(string tempId, int millisecondsTimeout)
{
var updatedTempId = tempId + UpdatedSuffix;
return Task.Run(() =>
{
var db = GetDatabase();
var watch = new Stopwatch();
watch.Start();
while(true)
{
// poll very often, we need to take over as fast as we can
Thread.Sleep(100);
try
{
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a read lock
_sqlServerSyntax.ReadLock(db, Constants.Locks.MainDom);
// the row
var mainDomRows = db.Fetch<KeyValueDto>("SELECT * FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
if (mainDomRows.Count == 0 || mainDomRows[0].Value == updatedTempId)
{
// the other main dom has updated our record
// Or the other maindom shutdown super fast and just deleted the record
// which indicates that we
// can acquire it and it has shutdown.
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
// so now we update the row with our appdomain id
InsertLockRecord(_lockId);
_logger.Debug<SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
return true;
}
else if (mainDomRows.Count == 1 && !mainDomRows[0].Value.StartsWith(tempId))
{
// in this case, the prefixed ID is different which means
// another new AppDomain has come online and is wanting to take over. In that case, we will not
// acquire.
_logger.Debug<SqlMainDomLock>("Cannot acquire, another booting application detected.");
return false;
}
}
catch (Exception ex)
{
ResetDatabase();
if (IsLockTimeoutException(ex))
{
_logger.Error<SqlMainDomLock>(ex, "Sql timeout occurred, waiting for existing MainDom is canceled.");
_hasError = true;
return false;
}
// unexpected
_logger.Error<SqlMainDomLock>(ex, "Unexpected error, waiting for existing MainDom is canceled.");
_hasError = true;
return false;
}
finally
{
db?.CompleteTransaction();
}
if (watch.ElapsedMilliseconds >= millisecondsTimeout)
{
// if the timeout has elapsed, it either means that the other main dom is taking too long to shutdown,
// or it could mean that the previous appdomain was terminated and didn't clear out the main dom SQL row
// and it's just been left as an orphan row.
// There's really know way of knowing unless we are constantly updating the row for the current maindom
// which isn't ideal.
// So... we're going to 'just' take over, if the writelock works then we'll assume we're ok
_logger.Debug<SqlMainDomLock>("Timeout elapsed, assuming orphan row, acquiring MainDom.");
try
{
db.BeginTransaction(IsolationLevel.ReadCommitted);
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
// so now we update the row with our appdomain id
InsertLockRecord(_lockId);
_logger.Debug<SqlMainDomLock>("Acquired with ID {LockId}", _lockId);
return true;
}
catch (Exception ex)
{
ResetDatabase();
if (IsLockTimeoutException(ex))
{
// something is wrong, we cannot acquire, not much we can do
_logger.Error<SqlMainDomLock>(ex, "Sql timeout occurred, could not forcibly acquire MainDom.");
_hasError = true;
return false;
}
_logger.Error<SqlMainDomLock>(ex, "Unexpected error, could not forcibly acquire MainDom.");
_hasError = true;
return false;
}
finally
{
db?.CompleteTransaction();
}
}
}
}, _cancellationTokenSource.Token);
}
/// <summary>
/// Inserts or updates the key/value row
/// </summary>
private RecordPersistenceType InsertLockRecord(string id)
{
var db = GetDatabase();
return db.InsertOrUpdate(new KeyValueDto
{
Key = MainDomKey,
Value = id,
Updated = DateTime.Now
});
}
/// <summary>
/// Checks if the DB row value is equals the value
/// </summary>
/// <returns></returns>
private bool IsMainDomValue(string val)
{
var db = GetDatabase();
return db.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoKeyValue WHERE [key] = @key AND [value] = @val",
new { key = MainDomKey, val = val }) == 1;
}
/// <summary>
/// Checks if the exception is an SQL timeout
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
private bool IsLockTimeoutException(Exception exception) => exception is SqlException sqlException && sqlException.Number == 1222;
#region IDisposable Support
private bool _disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
lock (_locker)
{
// immediately cancel all sub-tasks, we don't want them to keep querying
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
var db = GetDatabase();
try
{
db.BeginTransaction(IsolationLevel.ReadCommitted);
// get a write lock
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
// When we are disposed, it means we have released the MainDom lock
// and called all MainDom release callbacks, in this case
// if another maindom is actually coming online we need
// to signal to the MainDom coming online that we have shutdown.
// To do that, we update the existing main dom DB record with a suffixed "_updated" string.
// Otherwise, if we are just shutting down, we want to just delete the row.
if (_mainDomChanging)
{
_logger.Debug<SqlMainDomLock>("Releasing MainDom, updating row, new application is booting.");
db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey });
}
else
{
_logger.Debug<SqlMainDomLock>("Releasing MainDom, deleting row, application is shutting down.");
db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
}
}
catch (Exception ex)
{
ResetDatabase();
_logger.Error<SqlMainDomLock>(ex, "Unexpected error during dipsose.");
_hasError = true;
}
finally
{
db?.CompleteTransaction();
ResetDatabase();
}
}
}
_disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
}
#endregion
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping
/// Provides scopes.
/// </summary>
public interface IScopeProvider
{
{
/// <summary>
/// Creates an ambient scope.
/// </summary>
+107 -59
View File
@@ -8,136 +8,162 @@ namespace Umbraco.Core.Services
public interface IRelationService : IService
{
/// <summary>
/// Gets a <see cref="Relation"/> by its Id
/// Gets a <see cref="IRelation"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="Relation"/></param>
/// <returns>A <see cref="Relation"/> object</returns>
/// <param name="id">Id of the <see cref="IRelation"/></param>
/// <returns>A <see cref="IRelation"/> object</returns>
IRelation GetById(int id);
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// Gets a <see cref="IRelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <param name="id">Id of the <see cref="IRelationType"/></param>
/// <returns>A <see cref="IRelationType"/> object</returns>
IRelationType GetRelationTypeById(int id);
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// Gets a <see cref="IRelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <param name="id">Id of the <see cref="IRelationType"/></param>
/// <returns>A <see cref="IRelationType"/> object</returns>
IRelationType GetRelationTypeById(Guid id);
/// <summary>
/// Gets a <see cref="RelationType"/> by its Alias
/// Gets a <see cref="IRelationType"/> by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <param name="alias">Alias of the <see cref="IRelationType"/></param>
/// <returns>A <see cref="IRelationType"/> object</returns>
IRelationType GetRelationTypeByAlias(string alias);
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// Gets all <see cref="IRelation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetAllRelations(params int[] ids);
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>
/// Gets all <see cref="IRelation"/> objects by their <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(RelationType relationType);
/// <param name="relationType"><see cref="IRelation"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(IRelationType relationType);
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>'s Id
/// Gets all <see cref="IRelation"/> objects by their <see cref="IRelationType"/>'s Id
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeId">Id of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetAllRelationsByRelationType(int relationTypeId);
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// Gets all <see cref="IRelation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relationtypes for</param>
/// <returns>An enumerable list of <see cref="RelationType"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelationType> GetAllRelationTypes(params int[] ids);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent Id
/// Gets a list of <see cref="IRelation"/> objects by their parent Id
/// </summary>
/// <param name="id">Id of the parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParentId(int id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// Gets a list of <see cref="IRelation"/> objects by their parent Id
/// </summary>
/// <param name="id">Id of the parent to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="IRelation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParent(IUmbracoEntity parent);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// Gets a list of <see cref="IRelation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Id
/// Gets a list of <see cref="IRelation"/> objects by their child Id
/// </summary>
/// <param name="id">Id of the child to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChildId(int id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// Gets a list of <see cref="IRelation"/> objects by their child Id
/// </summary>
/// <param name="id">Id of the child to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChildId(int id, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="IRelation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChild(IUmbracoEntity child);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// Gets a list of <see cref="IRelation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child or parent Id.
/// Gets a list of <see cref="IRelation"/> objects by their child or parent Id.
/// Using this method will get you all relations regards of it being a child or parent relation.
/// </summary>
/// <param name="id">Id of the child or parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByParentOrChildId(int id);
IEnumerable<IRelation> GetByParentOrChildId(int id, string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
/// Gets a list of <see cref="IRelation"/> objects by the Name of the <see cref="IRelationType"/>
/// </summary>
/// <param name="relationTypeName">Name of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeName">Name of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByRelationTypeName(string relationTypeName);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Alias of the <see cref="RelationType"/>
/// Gets a list of <see cref="IRelation"/> objects by the Alias of the <see cref="IRelationType"/>
/// </summary>
/// <param name="relationTypeAlias">Alias of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeAlias">Alias of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByRelationTypeAlias(string relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Id of the <see cref="RelationType"/>
/// Gets a list of <see cref="IRelation"/> objects by the Id of the <see cref="IRelationType"/>
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <param name="relationTypeId">Id of the <see cref="IRelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="IRelation"/> objects</returns>
IEnumerable<IRelation> GetByRelationTypeId(int relationTypeId);
/// <summary>
/// Gets a paged result of <see cref="IRelation"/>
/// </summary>
/// <param name="relationTypeId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalChildren"></param>
/// <returns></returns>
IEnumerable<IRelation> GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null);
/// <summary>
/// Gets the Child object from a Relation as an <see cref="IUmbracoEntity"/>
/// </summary>
@@ -173,6 +199,26 @@ namespace Umbraco.Core.Services
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
IEnumerable<IUmbracoEntity> GetParentEntitiesFromRelations(IEnumerable<IRelation> relations);
/// <summary>
/// Returns paged parent entities for a related child id
/// </summary>
/// <param name="id"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalChildren"></param>
/// <returns></returns>
IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
/// <summary>
/// Returns paged child entities for a related parent id
/// </summary>
/// <param name="id"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalChildren"></param>
/// <returns></returns>
IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes);
/// <summary>
/// Gets the Parent and Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
@@ -186,7 +232,7 @@ namespace Umbraco.Core.Services
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(int parentId, int childId, IRelationType relationType);
/// <summary>
@@ -195,7 +241,7 @@ namespace Umbraco.Core.Services
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType);
/// <summary>
@@ -204,7 +250,7 @@ namespace Umbraco.Core.Services
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(int parentId, int childId, string relationTypeAlias);
/// <summary>
@@ -213,14 +259,14 @@ namespace Umbraco.Core.Services
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <returns>The created <see cref="IRelation"/></returns>
IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
/// <summary>
/// Checks whether any relations exists for the passed in <see cref="RelationType"/>.
/// Checks whether any relations exists for the passed in <see cref="IRelationType"/>.
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to check for relations</param>
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="RelationType"/>, otherwise <c>False</c></returns>
/// <param name="relationType"><see cref="IRelationType"/> to check for relations</param>
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="IRelationType"/>, otherwise <c>False</c></returns>
bool HasRelations(IRelationType relationType);
/// <summary>
@@ -265,33 +311,35 @@ namespace Umbraco.Core.Services
bool AreRelated(int parentId, int childId, string relationTypeAlias);
/// <summary>
/// Saves a <see cref="Relation"/>
/// Saves a <see cref="IRelation"/>
/// </summary>
/// <param name="relation">Relation to save</param>
void Save(IRelation relation);
void Save(IEnumerable<IRelation> relations);
/// <summary>
/// Saves a <see cref="RelationType"/>
/// Saves a <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Save</param>
void Save(IRelationType relationType);
/// <summary>
/// Deletes a <see cref="Relation"/>
/// Deletes a <see cref="IRelation"/>
/// </summary>
/// <param name="relation">Relation to Delete</param>
void Delete(IRelation relation);
/// <summary>
/// Deletes a <see cref="RelationType"/>
/// Deletes a <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Delete</param>
void Delete(IRelationType relationType);
/// <summary>
/// Deletes all <see cref="Relation"/> objects based on the passed in <see cref="RelationType"/>
/// Deletes all <see cref="IRelation"/> objects based on the passed in <see cref="IRelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to Delete Relations for</param>
/// <param name="relationType"><see cref="IRelationType"/> to Delete Relations for</param>
void DeleteRelationsOfType(IRelationType relationType);
}
}
@@ -25,11 +25,7 @@ namespace Umbraco.Core.Services.Implement
_entityService = entityService ?? throw new ArgumentNullException(nameof(entityService));
}
/// <summary>
/// Gets a <see cref="Relation"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="Relation"/></param>
/// <returns>A <see cref="Relation"/> object</returns>
/// <inheritdoc />
public IRelation GetById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -38,11 +34,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <inheritdoc />
public IRelationType GetRelationTypeById(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -51,11 +43,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a <see cref="RelationType"/> by its Id
/// </summary>
/// <param name="id">Id of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
/// <inheritdoc />
public IRelationType GetRelationTypeById(Guid id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -64,25 +52,10 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a <see cref="RelationType"/> by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="RelationType"/></param>
/// <returns>A <see cref="RelationType"/> object</returns>
public IRelationType GetRelationTypeByAlias(string alias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelationType>().Where(x => x.Alias == alias);
return _relationTypeRepository.Get(query).FirstOrDefault();
}
}
/// <inheritdoc />
public IRelationType GetRelationTypeByAlias(string alias) => GetRelationType(alias);
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetAllRelations(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -91,21 +64,13 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetAllRelationsByRelationType(RelationType relationType)
/// <inheritdoc />
public IEnumerable<IRelation> GetAllRelationsByRelationType(IRelationType relationType)
{
return GetAllRelationsByRelationType(relationType.Id);
}
/// <summary>
/// Gets all <see cref="Relation"/> objects by their <see cref="RelationType"/>'s Id
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetAllRelationsByRelationType(int relationTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -115,11 +80,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets all <see cref="Relation"/> objects
/// </summary>
/// <param name="ids">Optional array of integer ids to return relationtypes for</param>
/// <returns>An enumerable list of <see cref="RelationType"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelationType> GetAllRelationTypes(params int[] ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -128,82 +89,65 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent Id
/// </summary>
/// <param name="id">Id of the parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByParentId(int id)
/// <inheritdoc />
public IEnumerable<IRelation> GetByParentId(int id) => GetByParentId(id, null);
/// <inheritdoc />
public IEnumerable<IRelation> GetByParentId(int id, string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelation>().Where(x => x.ParentId == id);
return _relationRepository.Get(query);
if (relationTypeAlias.IsNullOrWhiteSpace())
{
var qry1 = Query<IRelation>().Where(x => x.ParentId == id);
return _relationRepository.Get(qry1);
}
var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty<IRelation>();
var qry2 = Query<IRelation>().Where(x => x.ParentId == id && x.RelationTypeId == relationType.Id);
return _relationRepository.Get(qry2);
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent)
{
return GetByParentId(parent.Id);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent) => GetByParentId(parent.Id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their parent entity
/// </summary>
/// <param name="parent">Parent Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias)
{
return GetByParent(parent).Where(relation => relation.RelationType.Alias == relationTypeAlias);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByParent(IUmbracoEntity parent, string relationTypeAlias) => GetByParentId(parent.Id, relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Id
/// </summary>
/// <param name="id">Id of the child to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByChildId(int id)
/// <inheritdoc />
public IEnumerable<IRelation> GetByChildId(int id) => GetByChildId(id, null);
/// <inheritdoc />
public IEnumerable<IRelation> GetByChildId(int id, string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelation>().Where(x => x.ChildId == id);
return _relationRepository.Get(query);
if (relationTypeAlias.IsNullOrWhiteSpace())
{
var qry1 = Query<IRelation>().Where(x => x.ChildId == id);
return _relationRepository.Get(qry1);
}
var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty<IRelation>();
var qry2 = Query<IRelation>().Where(x => x.ChildId == id && x.RelationTypeId == relationType.Id);
return _relationRepository.Get(qry2);
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child)
{
return GetByChildId(child.Id);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child) => GetByChildId(child.Id);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child Entity
/// </summary>
/// <param name="child">Child Entity to retrieve relations for</param>
/// <param name="relationTypeAlias">Alias of the type of relation to retrieve</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias)
{
return GetByChild(child).Where(relation => relation.RelationType.Alias == relationTypeAlias);
}
/// <inheritdoc />
public IEnumerable<IRelation> GetByChild(IUmbracoEntity child, string relationTypeAlias) => GetByChildId(child.Id, relationTypeAlias);
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by their child or parent Id.
/// Using this method will get you all relations regards of it being a child or parent relation.
/// </summary>
/// <param name="id">Id of the child or parent to retrieve relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByParentOrChildId(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -217,8 +161,7 @@ namespace Umbraco.Core.Services.Implement
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var rtQuery = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
var relationType = _relationTypeRepository.Get(rtQuery).FirstOrDefault();
var relationType = GetRelationType(relationTypeAlias);
if (relationType == null)
return Enumerable.Empty<IRelation>();
@@ -227,16 +170,13 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Name of the <see cref="RelationType"/>
/// </summary>
/// <param name="relationTypeName">Name of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByRelationTypeName(string relationTypeName)
{
List<int> relationTypeIds;
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
//This is a silly query - but i guess it's needed in case someone has more than one relation with the same Name (not alias), odd.
var query = Query<IRelationType>().Where(x => x.Name == relationTypeName);
var relationTypes = _relationTypeRepository.Get(query);
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
@@ -247,31 +187,17 @@ namespace Umbraco.Core.Services.Implement
: GetRelationsByListOfTypeIds(relationTypeIds);
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Alias of the <see cref="RelationType"/>
/// </summary>
/// <param name="relationTypeAlias">Alias of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByRelationTypeAlias(string relationTypeAlias)
{
List<int> relationTypeIds;
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
var relationTypes = _relationTypeRepository.Get(query);
relationTypeIds = relationTypes.Select(x => x.Id).ToList();
}
return relationTypeIds.Count == 0
var relationType = GetRelationType(relationTypeAlias);
return relationType == null
? Enumerable.Empty<IRelation>()
: GetRelationsByListOfTypeIds(relationTypeIds);
: GetRelationsByListOfTypeIds(new[] { relationType.Id });
}
/// <summary>
/// Gets a list of <see cref="Relation"/> objects by the Id of the <see cref="RelationType"/>
/// </summary>
/// <param name="relationTypeId">Id of the <see cref="RelationType"/> to retrieve Relations for</param>
/// <returns>An enumerable list of <see cref="Relation"/> objects</returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetByRelationTypeId(int relationTypeId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -281,37 +207,35 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets the Child object from a Relation as an <see cref="IUmbracoEntity"/>
/// </summary>
/// <param name="relation">Relation to retrieve child object from</param>
/// <returns>An <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IRelation> GetPagedByRelationTypeId(int relationTypeId, long pageIndex, int pageSize, out long totalRecords, Ordering ordering = null)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationTypeId);
return _relationRepository.GetPagedRelationsByQuery(query, pageIndex, pageSize, out totalRecords, ordering);
}
}
/// <inheritdoc />
public IUmbracoEntity GetChildEntityFromRelation(IRelation relation)
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
var objectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
return _entityService.Get(relation.ChildId, objectType);
}
/// <summary>
/// Gets the Parent object from a Relation as an <see cref="IUmbracoEntity"/>
/// </summary>
/// <param name="relation">Relation to retrieve parent object from</param>
/// <returns>An <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IUmbracoEntity GetParentEntityFromRelation(IRelation relation)
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
var objectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
return _entityService.Get(relation.ParentId, objectType);
}
/// <summary>
/// Gets the Parent and Child objects from a Relation as a <see cref="Tuple"/>"/> with <see cref="IUmbracoEntity"/>.
/// </summary>
/// <param name="relation">Relation to retrieve parent and child object from</param>
/// <returns>Returns a Tuple with Parent (item1) and Child (item2)</returns>
/// <inheritdoc />
public Tuple<IUmbracoEntity, IUmbracoEntity> GetEntitiesFromRelation(IRelation relation)
{
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
var child = _entityService.Get(relation.ChildId, childObjectType);
var parent = _entityService.Get(relation.ParentId, parentObjectType);
@@ -319,45 +243,63 @@ namespace Umbraco.Core.Services.Implement
return new Tuple<IUmbracoEntity, IUmbracoEntity>(parent, child);
}
/// <summary>
/// Gets the Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
/// <param name="relations">List of relations to retrieve child objects from</param>
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetChildEntitiesFromRelations(IEnumerable<IRelation> relations)
{
foreach (var relation in relations)
// Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll
// method to lookup batches of entities for each parent object type
foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ChildObjectType)))
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
yield return _entityService.Get(relation.ChildId, objectType);
var objectType = groupedRelations.Key;
var ids = groupedRelations.Select(x => x.ChildId).ToArray();
foreach (var e in _entityService.GetAll(objectType, ids))
yield return e;
}
}
/// <summary>
/// Gets the Parent objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
/// <param name="relations">List of relations to retrieve parent objects from</param>
/// <returns>An enumerable list of <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetParentEntitiesFromRelations(IEnumerable<IRelation> relations)
{
foreach (var relation in relations)
// Trying to avoid full N+1 lookups, so we'll group by the object type and then use the GetAll
// method to lookup batches of entities for each parent object type
foreach (var groupedRelations in relations.GroupBy(x => ObjectTypes.GetUmbracoObjectType(x.ParentObjectType)))
{
var objectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
yield return _entityService.Get(relation.ParentId, objectType);
var objectType = groupedRelations.Key;
var ids = groupedRelations.Select(x => x.ParentId).ToArray();
foreach (var e in _entityService.GetAll(objectType, ids))
yield return e;
}
}
/// <summary>
/// Gets the Parent and Child objects from a list of Relations as a list of <see cref="IUmbracoEntity"/> objects.
/// </summary>
/// <param name="relations">List of relations to retrieve parent and child objects from</param>
/// <returns>An enumerable list of <see cref="Tuple"/> with <see cref="IUmbracoEntity"/></returns>
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetPagedParentEntitiesByChildId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _relationRepository.GetPagedParentEntitiesByChildId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray());
}
}
/// <inheritdoc />
public IEnumerable<IUmbracoEntity> GetPagedChildEntitiesByParentId(int id, long pageIndex, int pageSize, out long totalChildren, params UmbracoObjectTypes[] entityTypes)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
return _relationRepository.GetPagedChildEntitiesByParentId(id, pageIndex, pageSize, out totalChildren, entityTypes.Select(x => x.GetGuid()).ToArray());
}
}
/// <inheritdoc />
public IEnumerable<Tuple<IUmbracoEntity, IUmbracoEntity>> GetEntitiesFromRelations(IEnumerable<IRelation> relations)
{
//TODO: Argh! N+1
foreach (var relation in relations)
{
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.RelationType.ParentObjectType);
var childObjectType = ObjectTypes.GetUmbracoObjectType(relation.ChildObjectType);
var parentObjectType = ObjectTypes.GetUmbracoObjectType(relation.ParentObjectType);
var child = _entityService.Get(relation.ChildId, childObjectType);
var parent = _entityService.Get(relation.ParentId, parentObjectType);
@@ -366,19 +308,15 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Relates two objects by their entity Ids.
/// </summary>
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(int parentId, int childId, IRelationType relationType)
{
// Ensure that the RelationType has an identity before using it to relate two entities
if (relationType.HasIdentity == false)
Save(relationType);
//TODO: We don't check if this exists first, it will throw some sort of data integrity exception if it already exists, is that ok?
var relation = new Relation(parentId, childId, relationType);
using (var scope = ScopeProvider.CreateScope())
@@ -398,25 +336,13 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Relates two objects that are based on the <see cref="IUmbracoEntity"/> interface.
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationType">The type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, IRelationType relationType)
{
return Relate(parent.Id, child.Id, relationType);
}
/// <summary>
/// Relates two objects by their entity Ids.
/// </summary>
/// <param name="parentId">Id of the parent</param>
/// <param name="childId">Id of the child</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(int parentId, int childId, string relationTypeAlias)
{
var relationType = GetRelationTypeByAlias(relationTypeAlias);
@@ -426,13 +352,7 @@ namespace Umbraco.Core.Services.Implement
return Relate(parentId, childId, relationType);
}
/// <summary>
/// Relates two objects that are based on the <see cref="IUmbracoEntity"/> interface.
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>The created <see cref="Relation"/></returns>
/// <inheritdoc />
public IRelation Relate(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
{
var relationType = GetRelationTypeByAlias(relationTypeAlias);
@@ -442,11 +362,7 @@ namespace Umbraco.Core.Services.Implement
return Relate(parent.Id, child.Id, relationType);
}
/// <summary>
/// Checks whether any relations exists for the passed in <see cref="RelationType"/>.
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to check for relations</param>
/// <returns>Returns <c>True</c> if any relations exists for the given <see cref="RelationType"/>, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool HasRelations(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -456,11 +372,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether any relations exists for the passed in Id.
/// </summary>
/// <param name="id">Id of an object to check relations for</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Id, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool IsRelated(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -470,12 +382,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Ids, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(int parentId, int childId)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -485,13 +392,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether two items are related with a given relation type alias
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <param name="relationTypeAlias">Alias of the relation type</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Ids and relation type, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(int parentId, int childId, string relationTypeAlias)
{
var relType = GetRelationTypeByAlias(relationTypeAlias);
@@ -502,13 +403,7 @@ namespace Umbraco.Core.Services.Implement
}
/// <summary>
/// Checks whether two items are related with a given relation type
/// </summary>
/// <param name="parentId">Id of the Parent relation</param>
/// <param name="childId">Id of the Child relation</param>
/// <param name="relationType">Type of relation</param>
/// <returns>Returns <c>True</c> if any relations exists with the given Ids and relation type, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(int parentId, int childId, IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
@@ -518,34 +413,20 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child)
{
return AreRelated(parent.Id, child.Id);
}
/// <summary>
/// Checks whether two items are related
/// </summary>
/// <param name="parent">Parent entity</param>
/// <param name="child">Child entity</param>
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
/// <inheritdoc />
public bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias)
{
return AreRelated(parent.Id, child.Id, relationTypeAlias);
}
/// <summary>
/// Saves a <see cref="Relation"/>
/// </summary>
/// <param name="relation">Relation to save</param>
/// <inheritdoc />
public void Save(IRelation relation)
{
using (var scope = ScopeProvider.CreateScope())
@@ -564,10 +445,25 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Saves a <see cref="RelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Save</param>
public void Save(IEnumerable<IRelation> relations)
{
using (var scope = ScopeProvider.CreateScope())
{
var saveEventArgs = new SaveEventArgs<IRelation>(relations);
if (scope.Events.DispatchCancelable(SavingRelation, this, saveEventArgs))
{
scope.Complete();
return;
}
_relationRepository.Save(relations);
scope.Complete();
saveEventArgs.CanCancel = false;
scope.Events.Dispatch(SavedRelation, this, saveEventArgs);
}
}
/// <inheritdoc />
public void Save(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope())
@@ -586,10 +482,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Deletes a <see cref="Relation"/>
/// </summary>
/// <param name="relation">Relation to Delete</param>
/// <inheritdoc />
public void Delete(IRelation relation)
{
using (var scope = ScopeProvider.CreateScope())
@@ -608,10 +501,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Deletes a <see cref="RelationType"/>
/// </summary>
/// <param name="relationType">RelationType to Delete</param>
/// <inheritdoc />
public void Delete(IRelationType relationType)
{
using (var scope = ScopeProvider.CreateScope())
@@ -630,10 +520,7 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Deletes all <see cref="Relation"/> objects based on the passed in <see cref="RelationType"/>
/// </summary>
/// <param name="relationType"><see cref="RelationType"/> to Delete Relations for</param>
/// <inheritdoc />
public void DeleteRelationsOfType(IRelationType relationType)
{
var relations = new List<IRelation>();
@@ -642,6 +529,8 @@ namespace Umbraco.Core.Services.Implement
var query = Query<IRelation>().Where(x => x.RelationTypeId == relationType.Id);
relations.AddRange(_relationRepository.Get(query).ToList());
//TODO: N+1, we should be able to do this in a single call
foreach (var relation in relations)
_relationRepository.Delete(relation);
@@ -653,6 +542,15 @@ namespace Umbraco.Core.Services.Implement
#region Private Methods
private IRelationType GetRelationType(string relationTypeAlias)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query<IRelationType>().Where(x => x.Alias == relationTypeAlias);
return _relationTypeRepository.Get(query).FirstOrDefault();
}
}
private IEnumerable<IRelation> GetRelationsByListOfTypeIds(IEnumerable<int> relationTypeIds)
{
var relations = new List<IRelation>();
+2 -2
View File
@@ -233,7 +233,7 @@ namespace Umbraco.Core
// just pick every service connectors - just making sure that not two of them
// would register the same entity type, with different udi types (would not make
// much sense anyways).
var connectors = Current.TypeLoader.GetTypes<IServiceConnector>();
var connectors = Current.HasFactory ? (Current.TypeLoader?.GetTypes<IServiceConnector>() ?? Enumerable.Empty<Type>()) : Enumerable.Empty<Type>();
var result = new Dictionary<string, UdiType>();
foreach (var connector in connectors)
{
@@ -368,7 +368,7 @@ namespace Umbraco.Core
return (udi1 == udi2) == false;
}
private class UnknownTypeUdi : Udi
internal class UnknownTypeUdi : Udi
{
private UnknownTypeUdi()
: base("unknown", "umb://unknown/")
+14 -3
View File
@@ -128,6 +128,10 @@
</Compile>
-->
<Compile Include="AssemblyExtensions.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\AddMainDomLock.cs" />
<Compile Include="Runtime\IMainDomLock.cs" />
<Compile Include="Runtime\MainDomSemaphoreLock.cs" />
<Compile Include="Runtime\SqlMainDomLock.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\MissingContentVersionsIndexes.cs" />
<Compile Include="SystemLock.cs" />
<Compile Include="Attempt.cs" />
@@ -252,6 +256,9 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\AddNewRelationTypes.cs" />
<Compile Include="Migrations\Upgrade\V_8_6_0\UpdateRelationTypeTable.cs" />
<Compile Include="Models\Editors\UmbracoEntityReference.cs" />
<Compile Include="Models\ContentEditing\ContentAppBadge.cs" />
<Compile Include="Models\ContentEditing\ContentAppBadgeType.cs" />
<Compile Include="Models\Entities\EntityExtensions.cs" />
@@ -282,6 +289,10 @@
<Compile Include="Models\PublishedContent\IPublishedContentType.cs" />
<Compile Include="Models\PublishedContent\IPublishedPropertyType.cs" />
<Compile Include="PropertyEditors\ConfigurationFieldsExtensions.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollection.cs" />
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionBuilder.cs" />
<Compile Include="PropertyEditors\IDataValueReference.cs" />
<Compile Include="PropertyEditors\IDataValueReferenceFactory.cs" />
<Compile Include="PropertyEditors\IDataEditorWithMediaPath.cs" />
<Compile Include="PropertyEditors\IIgnoreUserStartNodesConfig.cs" />
<Compile Include="PublishedContentExtensions.cs" />
@@ -403,7 +414,7 @@
<Compile Include="Events\ExportedMemberEventArgs.cs" />
<Compile Include="Events\RolesEventArgs.cs" />
<Compile Include="Events\UserGroupWithUsers.cs" />
<Compile Include="IMainDom.cs" />
<Compile Include="Runtime\IMainDom.cs" />
<Compile Include="IO\IFileSystems.cs" />
<Compile Include="IO\IMediaFileSystem.cs" />
<Compile Include="GuidUtils.cs" />
@@ -734,7 +745,7 @@
<Compile Include="Logging\ProfilingLogger.cs" />
<Compile Include="Logging\VoidProfiler.cs" />
<Compile Include="Macros\MacroErrorBehaviour.cs" />
<Compile Include="MainDom.cs" />
<Compile Include="Runtime\MainDom.cs" />
<Compile Include="Manifest\ManifestParser.cs" />
<Compile Include="Manifest\ValueValidatorConverter.cs" />
<Compile Include="Manifest\ManifestWatcher.cs" />
@@ -1531,7 +1542,7 @@
<Compile Include="TypeExtensions.cs" />
<Compile Include="Udi.cs" />
<Compile Include="UdiDefinitionAttribute.cs" />
<Compile Include="UdiEntityType.cs" />
<Compile Include="Contants-UdiEntityType.cs" />
<Compile Include="UdiGetterExtensions.cs" />
<Compile Include="UdiRange.cs" />
<Compile Include="UdiType.cs" />
@@ -30,9 +30,8 @@ namespace Umbraco.TestData
if (ct.Variations.VariesBySegment())
return Content($"The document type {alias} already allows segments, nothing has been changed");
ct.Variations = ct.Variations.SetFlag(ContentVariation.Segment);
propType.Variations = propType.Variations.SetFlag(ContentVariation.Segment);
ct.SetVariesBy(ContentVariation.Segment);
propType.SetVariesBy(ContentVariation.Segment);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments");
@@ -50,7 +49,7 @@ namespace Umbraco.TestData
if (!ct.VariesBySegment())
return Content($"The document type {alias} does not allow segments, nothing has been changed");
ct.Variations = ct.Variations.UnsetFlag(ContentVariation.Segment);
ct.SetVariesBy(ContentVariation.Segment, false);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} no longer allows segments");
+80 -46
View File
@@ -223,7 +223,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
// gen 1
d.Set(1, "one");
Assert.AreEqual(1, d.Test.GetValues(1).Length);
@@ -321,7 +321,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
Assert.AreEqual(0, d.Test.GetValues(1).Length);
// gen 1
@@ -416,7 +416,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
// gen 1
d.Set(1, "one");
Assert.AreEqual(1, d.Test.GetValues(1).Length);
@@ -578,7 +578,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
d.Set(1, "one");
d.Set(2, "two");
@@ -689,7 +689,7 @@ namespace Umbraco.Tests.Cache
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
d.Set(1, "ein");
d.SetLocked(1, "ein");
Assert.AreEqual(3, d.Test.GetValues(1).Length);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -727,31 +727,25 @@ namespace Umbraco.Tests.Cache
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider))
Assert.Throws<InvalidOperationException>(() =>
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
Assert.AreNotSame(w1, w2); // get a new writer each time
d.Set(1, "one");
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
}
});
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(0, d.CreateSnapshot().Gen);
}
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(0, t.WLocked);
Assert.IsFalse(t.IsLocked);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, d.CreateSnapshot().Gen);
@@ -772,11 +766,14 @@ namespace Umbraco.Tests.Cache
using (var w1 = d.GetScopedWriteLock(scopeProvider))
{
// This one is interesting, although we don't allow recursive locks, since this is
// using the same ScopeContext/key, the lock acquisition is only done once
using (var w2 = d.GetScopedWriteLock(scopeProvider))
{
Assert.AreSame(w1, w2);
d.Set(1, "one");
d.SetLocked(1, "one");
}
}
}
@@ -797,19 +794,16 @@ namespace Umbraco.Tests.Cache
using (var w1 = d.GetScopedWriteLock(scopeProvider1))
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider2))
Assert.Throws<InvalidOperationException>(() =>
{
Assert.AreEqual(1, t.LiveGen);
Assert.AreEqual(2, t.WLocked);
Assert.IsTrue(t.NextGen);
using (var w2 = d.GetScopedWriteLock(scopeProvider2))
{
}
});
Assert.AreNotSame(w1, w2);
d.Set(1, "one");
}
}
}
@@ -848,13 +842,13 @@ namespace Umbraco.Tests.Cache
Assert.IsFalse(d.Test.NextGen);
Assert.AreEqual("uno", s2.Get(1));
var scopeProvider = GetScopeProvider();
var scopeProvider = GetScopeProvider();
using (d.GetScopedWriteLock(scopeProvider))
{
// gen 3
Assert.AreEqual(2, d.Test.GetValues(1).Length);
d.Set(1, "ein");
d.SetLocked(1, "ein");
Assert.AreEqual(3, d.Test.GetValues(1).Length);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -881,6 +875,7 @@ namespace Umbraco.Tests.Cache
{
var d = new SnapDictionary<int, string>();
d.Test.CollectAuto = false;
// gen 1
d.Set(1, "one");
@@ -894,12 +889,11 @@ namespace Umbraco.Tests.Cache
Assert.AreEqual("uno", s2.Get(1));
var scopeProvider = GetScopeProvider();
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
d.Set(1, "ein");
d.SetLocked(1, "ein");
var s3 = d.CreateSnapshot();
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
@@ -934,12 +928,11 @@ namespace Umbraco.Tests.Cache
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
d.Set(1, "ein");
d.SetLocked(1, "ein");
var s3 = d.CreateSnapshot();
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
@@ -967,7 +960,7 @@ namespace Umbraco.Tests.Cache
var d = new SnapDictionary<int, string>();
var t = d.Test;
t.CollectAuto = false;
// gen 1
d.Set(1, "one");
var s1 = d.CreateSnapshot();
@@ -984,12 +977,11 @@ namespace Umbraco.Tests.Cache
var scopeContext = new ScopeContext();
var scopeProvider = GetScopeProvider(scopeContext);
using (d.GetScopedWriteLock(scopeProvider))
{
// creating a snapshot in a write-lock does NOT return the "current" content
// it uses the previous snapshot, so new snapshot created only on release
d.Set(1, "ein");
d.SetLocked(1, "ein");
var s3 = d.CreateSnapshot();
Assert.AreEqual(2, s3.Gen);
Assert.AreEqual("uno", s3.Get(1));
@@ -997,7 +989,7 @@ namespace Umbraco.Tests.Cache
// we made some changes, so a next gen is required
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
// but live snapshot contains changes
var ls = t.LiveSnapshot;
@@ -1008,7 +1000,7 @@ namespace Umbraco.Tests.Cache
// nothing is committed until scope exits
Assert.AreEqual(3, t.LiveGen);
Assert.IsTrue(t.NextGen);
Assert.AreEqual(1, t.WLocked);
Assert.IsTrue(t.IsLocked);
// no changes until exit
var s4 = d.CreateSnapshot();
@@ -1020,7 +1012,7 @@ namespace Umbraco.Tests.Cache
// now things have changed
Assert.AreEqual(2, t.LiveGen);
Assert.IsFalse(t.NextGen);
Assert.AreEqual(0, t.WLocked);
Assert.IsFalse(t.IsLocked);
// no changes since not completed
var s5 = d.CreateSnapshot();
@@ -1097,9 +1089,10 @@ namespace Umbraco.Tests.Cache
// writer is scope contextual and scoped
// when disposed, nothing happens
// when the context exists, the writer is released
using (d.GetScopedWriteLock(scopeProvider))
{
d.Set(1, "ein");
d.SetLocked(1, "ein");
Assert.IsTrue(d.Test.NextGen);
Assert.AreEqual(3, d.Test.LiveGen);
Assert.IsNotNull(d.Test.GenObj);
@@ -1107,7 +1100,7 @@ namespace Umbraco.Tests.Cache
}
// writer has not released
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsTrue(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
@@ -1118,7 +1111,7 @@ namespace Umbraco.Tests.Cache
// panic!
var s2 = d.CreateSnapshot();
Assert.AreEqual(1, d.Test.WLocked);
Assert.IsTrue(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -1127,7 +1120,7 @@ namespace Umbraco.Tests.Cache
// release writer
scopeContext.ScopeExit(true);
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsFalse(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(2, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -1135,7 +1128,7 @@ namespace Umbraco.Tests.Cache
var s3 = d.CreateSnapshot();
Assert.AreEqual(0, d.Test.WLocked);
Assert.IsFalse(d.Test.IsLocked);
Assert.IsNotNull(d.Test.GenObj);
Assert.AreEqual(3, d.Test.GenObj.Gen);
Assert.AreEqual(3, d.Test.LiveGen);
@@ -1150,4 +1143,45 @@ namespace Umbraco.Tests.Cache
return scopeProvider;
}
}
/// <summary>
/// Used for tests so that we don't have to wrap every Set/Clear call in locks
/// </summary>
public static class SnapDictionaryExtensions
{
internal static void Set<TKey, TValue>(this SnapDictionary<TKey, TValue> d, TKey key, TValue value)
where TValue : class
{
using (d.GetScopedWriteLock(GetScopeProvider()))
{
d.SetLocked(key, value);
}
}
internal static void Clear<TKey, TValue>(this SnapDictionary<TKey, TValue> d)
where TValue : class
{
using (d.GetScopedWriteLock(GetScopeProvider()))
{
d.ClearLocked();
}
}
internal static void Clear<TKey, TValue>(this SnapDictionary<TKey, TValue> d, TKey key)
where TValue : class
{
using (d.GetScopedWriteLock(GetScopeProvider()))
{
d.ClearLocked(key);
}
}
private static IScopeProvider GetScopeProvider()
{
var scopeProvider = Mock.Of<IScopeProvider>();
Mock.Get(scopeProvider)
.Setup(x => x.Context).Returns(() => null);
return scopeProvider;
}
}
}
@@ -1,6 +1,7 @@
using NUnit.Framework;
using Umbraco.Web.Trees;
using System;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Web.Trees;
namespace Umbraco.Tests.CoreThings
{
@@ -22,6 +23,7 @@ namespace Umbraco.Tests.CoreThings
Assert.IsFalse(value.HasFlag(test));
}
[Obsolete]
[TestCase(TreeUse.Dialog, TreeUse.Dialog, true)]
[TestCase(TreeUse.Dialog, TreeUse.Main, false)]
[TestCase(TreeUse.Dialog | TreeUse.Main, TreeUse.Dialog, true)]
@@ -51,47 +53,5 @@ namespace Umbraco.Tests.CoreThings
else
Assert.IsFalse(value.HasFlagAny(test));
}
[TestCase(TreeUse.None, TreeUse.None, TreeUse.None)]
[TestCase(TreeUse.None, TreeUse.Main, TreeUse.Main)]
[TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.Dialog)]
[TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)]
[TestCase(TreeUse.Main, TreeUse.Main, TreeUse.Main)]
[TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)]
[TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.Dialog)]
[TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog)]
public void SetFlagTests(TreeUse value, TreeUse flag, TreeUse expected)
{
Assert.AreEqual(expected, value.SetFlag(flag));
}
[TestCase(TreeUse.None, TreeUse.None, TreeUse.None)]
[TestCase(TreeUse.None, TreeUse.Main, TreeUse.None)]
[TestCase(TreeUse.None, TreeUse.Dialog, TreeUse.None)]
[TestCase(TreeUse.None, TreeUse.Main | TreeUse.Dialog, TreeUse.None)]
[TestCase(TreeUse.Main, TreeUse.None, TreeUse.Main)]
[TestCase(TreeUse.Main, TreeUse.Main, TreeUse.None)]
[TestCase(TreeUse.Main, TreeUse.Dialog, TreeUse.Main)]
[TestCase(TreeUse.Main, TreeUse.Main | TreeUse.Dialog, TreeUse.None)]
[TestCase(TreeUse.Dialog, TreeUse.None, TreeUse.Dialog)]
[TestCase(TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)]
[TestCase(TreeUse.Dialog, TreeUse.Dialog, TreeUse.None)]
[TestCase(TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.None, TreeUse.Main | TreeUse.Dialog)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main, TreeUse.Dialog)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Dialog, TreeUse.Main)]
[TestCase(TreeUse.Main | TreeUse.Dialog, TreeUse.Main | TreeUse.Dialog, TreeUse.None)]
public void UnsetFlagTests(TreeUse value, TreeUse flag, TreeUse expected)
{
Assert.AreEqual(expected, value.UnsetFlag(flag));
}
}
}
@@ -9,6 +9,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Runtime;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Web;
@@ -14,6 +14,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Runtime;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Changes;
+2 -2
View File
@@ -12,7 +12,7 @@ namespace Umbraco.Tests.Models
[Test]
public void Can_Deep_Clone()
{
var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new Relation(9, 8, new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66
})
@@ -52,7 +52,7 @@ namespace Umbraco.Tests.Models
{
var ss = new SerializationService(new JsonNetSerializer());
var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new Relation(9, 8, new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66
})
@@ -12,7 +12,7 @@ namespace Umbraco.Tests.Models
[Test]
public void Can_Deep_Clone()
{
var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66,
CreateDate = DateTime.Now,
@@ -48,7 +48,7 @@ namespace Umbraco.Tests.Models
{
var ss = new SerializationService(new JsonNetSerializer());
var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test")
var item = new RelationType("test", "test", false, Guid.NewGuid(), Guid.NewGuid())
{
Id = 66,
CreateDate = DateTime.Now,
@@ -26,22 +26,22 @@ namespace Umbraco.Tests.Packaging
public class PackageDataInstallationTests : TestWithSomeContentBase
{
[HideFromTypeFinder]
[DataEditor("7e062c13-7c41-4ad9-b389-41d88aeef87c", "Editor1", "editor1")]
public class Editor1 : DataEditor
{
public Editor1(ILogger logger)
: base(logger)
{
Alias = "7e062c13-7c41-4ad9-b389-41d88aeef87c";
}
}
[HideFromTypeFinder]
[DataEditor("d15e1281-e456-4b24-aa86-1dda3e4299d5", "Editor2", "editor2")]
public class Editor2 : DataEditor
{
public Editor2(ILogger logger)
: base(logger)
{
Alias = "d15e1281-e456-4b24-aa86-1dda3e4299d5";
}
}
@@ -8,6 +8,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -35,7 +36,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches.Disabled);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, Logger, commonRepository, langRepository);
var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, Logger);
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(scopeAccessor);
var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(scopeAccessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -69,7 +69,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches);
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(scopeAccessor);
var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(scopeAccessor, appCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Moq;
@@ -6,6 +7,7 @@ using NUnit.Framework;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -25,7 +27,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
languageRepository = new LanguageRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, commonRepository, languageRepository);
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
documentRepository = new DocumentRepository(accessor, Core.Cache.AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
var domainRepository = new DomainRepository(accessor, Core.Cache.AppCaches.Disabled, Logger);
return domainRepository;
}
@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Persistence.Repositories
{
[TestFixture]
[UmbracoTest(Mapper = true, Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class EntityRepositoryTest : TestWithDatabaseBase
{
private EntityRepository CreateRepository(IScopeAccessor scopeAccessor)
{
var entityRepository = new EntityRepository(scopeAccessor);
return entityRepository;
}
[Test]
public void Get_Paged_Mixed_Entities_By_Ids()
{
//Create content
var createdContent = new List<IContent>();
var contentType = MockedContentTypes.CreateBasicContentType("blah");
ServiceContext.ContentTypeService.Save(contentType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedContent.CreateBasicContent(contentType);
ServiceContext.ContentService.Save(c1);
createdContent.Add(c1);
}
//Create media
var createdMedia = new List<IMedia>();
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(c1);
createdMedia.Add(c1);
}
// Create members
var memberType = MockedContentTypes.CreateSimpleMemberType("simple");
ServiceContext.MemberTypeService.Save(memberType);
var createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList();
ServiceContext.MemberService.Save(createdMembers);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repo = CreateRepository((IScopeAccessor)provider);
var ids = createdContent.Select(x => x.Id).Concat(createdMedia.Select(x => x.Id)).Concat(createdMembers.Select(x => x.Id));
var objectTypes = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media, Constants.ObjectTypes.Member };
var query = SqlContext.Query<IUmbracoEntity>()
.WhereIn(e => e.Id, ids);
var entities = repo.GetPagedResultsByQuery(query, objectTypes, 0, 20, out var totalRecords, null, null).ToList();
Assert.AreEqual(20, entities.Count);
Assert.AreEqual(30, totalRecords);
//add the next page
entities.AddRange(repo.GetPagedResultsByQuery(query, objectTypes, 1, 20, out totalRecords, null, null));
Assert.AreEqual(30, entities.Count);
Assert.AreEqual(30, totalRecords);
var contentEntities = entities.OfType<IDocumentEntitySlim>().ToList();
var mediaEntities = entities.OfType<IMediaEntitySlim>().ToList();
var memberEntities = entities.OfType<IMemberEntitySlim>().ToList();
Assert.AreEqual(10, contentEntities.Count);
Assert.AreEqual(10, mediaEntities.Count);
Assert.AreEqual(10, memberEntities.Count);
}
}
}
}
@@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Tests.Testing;
using Umbraco.Core.Services;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Tests.Persistence.Repositories
{
@@ -41,7 +42,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var languageRepository = new LanguageRepository(scopeAccessor, appCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, Logger, commonRepository, languageRepository);
var tagRepository = new TagRepository(scopeAccessor, appCaches, Logger);
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(scopeAccessor);
var relationRepository = new RelationRepository(scopeAccessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MediaRepository(scopeAccessor, appCaches, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -35,7 +36,12 @@ namespace Umbraco.Tests.Persistence.Repositories
memberTypeRepository = new MemberTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
memberGroupRepository = new MemberGroupRepository(accessor, AppCaches.Disabled, Logger);
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MemberRepository(accessor, AppCaches.Disabled, Logger, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -310,7 +311,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -6,6 +7,7 @@ using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
@@ -31,7 +33,8 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var accessor = (IScopeAccessor) provider;
relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of<ILogger>());
var repository = new RelationRepository(accessor, Mock.Of<ILogger>(), relationTypeRepository);
var entityRepository = new EntityRepository(accessor);
var repository = new RelationRepository(accessor, Mock.Of<ILogger>(), relationTypeRepository, entityRepository);
return repository;
}
@@ -168,6 +171,156 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Get_Paged_Parent_Entities_By_Child_Id()
{
CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider, out var relationTypeRepository);
// Get parent entities for child id
var parents = repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 11, out var totalRecords).ToList();
Assert.AreEqual(20, totalRecords);
Assert.AreEqual(11, parents.Count);
//add the next page
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 1, 11, out totalRecords));
Assert.AreEqual(20, totalRecords);
Assert.AreEqual(20, parents.Count);
var contentEntities = parents.OfType<IDocumentEntitySlim>().ToList();
var mediaEntities = parents.OfType<IMediaEntitySlim>().ToList();
var memberEntities = parents.OfType<IMemberEntitySlim>().ToList();
Assert.AreEqual(10, contentEntities.Count);
Assert.AreEqual(0, mediaEntities.Count);
Assert.AreEqual(10, memberEntities.Count);
//only of a certain type
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Document.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedParentEntitiesByChildId(createdMedia[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
Assert.AreEqual(0, totalRecords);
}
}
[Test]
public void Get_Paged_Parent_Child_Entities_With_Same_Entity_Relation()
{
//Create a media item and create a relationship between itself (parent -> child)
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
var media = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(media);
var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
ServiceContext.RelationService.Relate(media.Id, media.Id, relType);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider, out var relationTypeRepository);
// Get parent entities for child id
var parents = repository.GetPagedParentEntitiesByChildId(media.Id, 0, 10, out var totalRecords).ToList();
Assert.AreEqual(1, totalRecords);
Assert.AreEqual(1, parents.Count);
// Get child entities for parent id
var children = repository.GetPagedChildEntitiesByParentId(media.Id, 0, 10, out totalRecords).ToList();
Assert.AreEqual(1, totalRecords);
Assert.AreEqual(1, children.Count);
}
}
[Test]
public void Get_Paged_Child_Entities_By_Parent_Id()
{
CreateTestDataForPagingTests(out var createdContent, out var createdMembers, out var createdMedia);
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider, out var relationTypeRepository);
// Get parent entities for child id
var parents = repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 6, out var totalRecords).ToList();
Assert.AreEqual(10, totalRecords);
Assert.AreEqual(6, parents.Count);
//add the next page
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 1, 6, out totalRecords));
Assert.AreEqual(10, totalRecords);
Assert.AreEqual(10, parents.Count);
var contentEntities = parents.OfType<IDocumentEntitySlim>().ToList();
var mediaEntities = parents.OfType<IMediaEntitySlim>().ToList();
var memberEntities = parents.OfType<IMemberEntitySlim>().ToList();
Assert.AreEqual(0, contentEntities.Count);
Assert.AreEqual(10, mediaEntities.Count);
Assert.AreEqual(0, memberEntities.Count);
//only of a certain type
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdMembers[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Media.GetGuid()));
Assert.AreEqual(10, totalRecords);
parents.AddRange(repository.GetPagedChildEntitiesByParentId(createdContent[0].Id, 0, 100, out totalRecords, UmbracoObjectTypes.Member.GetGuid()));
Assert.AreEqual(0, totalRecords);
}
}
private void CreateTestDataForPagingTests(out List<IContent> createdContent, out List<IMember> createdMembers, out List<IMedia> createdMedia)
{
//Create content
createdContent = new List<IContent>();
var contentType = MockedContentTypes.CreateBasicContentType("blah");
ServiceContext.ContentTypeService.Save(contentType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedContent.CreateBasicContent(contentType);
ServiceContext.ContentService.Save(c1);
createdContent.Add(c1);
}
//Create media
createdMedia = new List<IMedia>();
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(c1);
createdMedia.Add(c1);
}
// Create members
var memberType = MockedContentTypes.CreateSimpleMemberType("simple");
ServiceContext.MemberTypeService.Save(memberType);
createdMembers = MockedMember.CreateSimpleMember(memberType, 10).ToList();
ServiceContext.MemberService.Save(createdMembers);
var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
// Relate content to media
foreach (var content in createdContent)
foreach (var media in createdMedia)
ServiceContext.RelationService.Relate(content.Id, media.Id, relType);
// Relate members to media
foreach (var member in createdMembers)
foreach (var media in createdMedia)
ServiceContext.RelationService.Relate(member.Id, media.Id, relType);
}
[Test]
public void Can_Perform_Exists_On_RelationRepository()
{
@@ -260,14 +413,24 @@ namespace Umbraco.Tests.Persistence.Repositories
public void CreateTestData()
{
var relateContent = new RelationType(Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" };
var relateContentType = new RelationType(Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" };
var relateContent = new RelationType(
"Relate Content on Copy", "relateContentOnCopy", true,
Constants.ObjectTypes.Document,
new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"));
var relateContentType = new RelationType("Relate ContentType on Copy",
"relateContentTypeOnCopy",
true,
Constants.ObjectTypes.DocumentType,
new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"));
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var relationTypeRepository = new RelationTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of<ILogger>());
var relationRepository = new RelationRepository((IScopeAccessor) provider, Mock.Of<ILogger>(), relationTypeRepository);
var accessor = (IScopeAccessor)provider;
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of<ILogger>());
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Mock.Of<ILogger>(), relationTypeRepository, entityRepository);
relationTypeRepository.Save(relateContent);
relationTypeRepository.Save(relateContentType);
@@ -42,9 +42,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var repository = CreateRepository(provider);
// Act
var relateMemberToContent = new RelationType(Constants.ObjectTypes.Member,
Constants.ObjectTypes.Document,
"relateMemberToContent") { IsBidirectional = true, Name = "Relate Member to Content" };
var relateMemberToContent = new RelationType("Relate Member to Content", "relateMemberToContent", true, Constants.ObjectTypes.Member, Constants.ObjectTypes.Document);
repository.Save(relateMemberToContent);
@@ -135,7 +133,7 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(relationTypes, Is.Not.Null);
Assert.That(relationTypes.Any(), Is.True);
Assert.That(relationTypes.Any(x => x == null), Is.False);
Assert.That(relationTypes.Count(), Is.EqualTo(5));
Assert.That(relationTypes.Count(), Is.EqualTo(7));
}
}
@@ -226,8 +224,8 @@ namespace Umbraco.Tests.Persistence.Repositories
public void CreateTestData()
{
var relateContent = new RelationType(Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"), "relateContentOnCopy") { IsBidirectional = true, Name = "Relate Content on Copy" };
var relateContentType = new RelationType(Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"), "relateContentTypeOnCopy") { IsBidirectional = true, Name = "Relate ContentType on Copy" };
var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"));
var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"));
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = ScopeProvider.CreateScope())
@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Cache;
@@ -7,6 +8,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -74,7 +76,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
// create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -104,7 +106,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -143,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -185,7 +187,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -225,7 +227,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -261,7 +263,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -305,7 +307,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -349,7 +351,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -394,7 +396,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -429,7 +431,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -469,7 +471,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -513,7 +515,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -557,7 +559,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -601,7 +603,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -646,7 +648,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -703,7 +705,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -755,7 +757,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
//create data to relate to
var contentType = MockedContentTypes.CreateSimpleContentType("test", "Test");
@@ -791,7 +793,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -871,7 +873,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var provider = TestObjects.GetScopeProvider(Logger);
using (ScopeProvider.CreateScope())
{
var contentRepository = CreateContentRepository(provider, out var contentTypeRepository);
var contentRepository = CreateDocumentRepository(provider, out var contentTypeRepository);
var mediaRepository = CreateMediaRepository(provider, out var mediaTypeRepository);
//create data to relate to
@@ -950,7 +952,7 @@ namespace Umbraco.Tests.Persistence.Repositories
return new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
}
private DocumentRepository CreateContentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository)
private DocumentRepository CreateDocumentRepository(IScopeProvider provider, out ContentTypeRepository contentTypeRepository)
{
var accessor = (IScopeAccessor) provider;
var templateRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
@@ -958,7 +960,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -970,7 +977,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches.Disabled);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MediaRepository(accessor, AppCaches.Disabled, Logger, mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
}
@@ -12,6 +12,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -237,11 +238,16 @@ namespace Umbraco.Tests.Persistence.Repositories
{
var templateRepository = CreateRepository(ScopeProvider);
var tagRepository = new TagRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var tagRepository = new TagRepository(ScopeProvider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(ScopeProvider, templateRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger);
var contentTypeRepository = new ContentTypeRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var contentRepo = new DocumentRepository((IScopeAccessor) ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var languageRepository = new LanguageRepository(ScopeProvider, AppCaches.Disabled, Logger);
var contentTypeRepository = new ContentTypeRepository(ScopeProvider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(ScopeProvider, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(ScopeProvider);
var relationRepository = new RelationRepository(ScopeProvider, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var contentRepo = new DocumentRepository(ScopeProvider, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage2", "Textpage");
ServiceContext.FileService.SaveTemplate(contentType.DefaultTemplate); // else, FK violation on contentType!
@@ -14,6 +14,8 @@ using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using System;
namespace Umbraco.Tests.Persistence.Repositories
{
@@ -29,7 +31,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
mediaTypeRepository = new MediaTypeRepository(accessor, AppCaches, Mock.Of<ILogger>(), commonRepository, languageRepository);
var tagRepository = new TagRepository(accessor, AppCaches, Mock.Of<ILogger>());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>());
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new MediaRepository(accessor, AppCaches, Mock.Of<ILogger>(), mediaTypeRepository, tagRepository, Mock.Of<ILanguageRepository>(), relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -47,7 +54,12 @@ namespace Umbraco.Tests.Persistence.Repositories
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -24,6 +24,7 @@ using Umbraco.Web.PropertyEditors;
namespace Umbraco.Tests.PropertyEditors
{
[TestFixture]
public class ImageCropperTest
{
@@ -11,6 +11,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Tests.PublishedContent;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
@@ -33,7 +34,7 @@ namespace Umbraco.Tests.Published
var proflog = new ProfilingLogger(logger, profiler);
PropertyEditorCollection editors = null;
var editor = new NestedContentPropertyEditor(logger, new Lazy<PropertyEditorCollection>(() => editors));
var editor = new NestedContentPropertyEditor(logger, new Lazy<PropertyEditorCollection>(() => editors), Mock.Of<IDataTypeService>(), Mock.Of<IContentTypeService>());
editors = new PropertyEditorCollection(new DataEditorCollection(new DataEditor[] { editor }));
var dataType1 = new DataType(editor)
@@ -918,6 +918,14 @@ namespace Umbraco.Tests.PublishedContent
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
_snapshotAccessor.PublishedSnapshot = snapshot;
var snapshotService = (PublishedSnapshotService)_snapshotService;
var contentStore = snapshotService.GetContentStore();
var parentNodes = contentStore.Test.GetValues(1);
var parentNode = parentNodes[0];
AssertLinkedNode(parentNode.contentNode, -1, -1, 2, 4, 6);
Assert.AreEqual(1, parentNode.gen);
var documents = snapshot.Content.GetAtRoot().ToArray();
AssertDocuments(documents, "N1", "N2", "N3");
@@ -934,6 +942,15 @@ namespace Umbraco.Tests.PublishedContent
new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshNode),
}, out _, out _);
parentNodes = contentStore.Test.GetValues(1);
Assert.AreEqual(2, parentNodes.Length);
parentNode = parentNodes[1]; // get the first gen
AssertLinkedNode(parentNode.contentNode, -1, -1, 2, 4, 6); // the structure should have remained the same
Assert.AreEqual(1, parentNode.gen);
parentNode = parentNodes[0]; // get the latest gen
AssertLinkedNode(parentNode.contentNode, -1, -1, 2, 4, 6); // the structure should have remained the same
Assert.AreEqual(2, parentNode.gen);
documents = snapshot.Content.GetAtRoot().ToArray();
AssertDocuments(documents, "N1", "N2", "N3");
@@ -942,6 +959,8 @@ namespace Umbraco.Tests.PublishedContent
documents = snapshot.Content.GetById(2).Children().ToArray();
AssertDocuments(documents, "N9", "N8", "N7");
}
[Test]
@@ -11,6 +11,7 @@ using Umbraco.Core.Models;
using Umbraco.Web.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.PublishedContent
{
@@ -38,9 +39,14 @@ namespace Umbraco.Tests.PublishedContent
base.Initialize();
var converters = Factory.GetInstance<PropertyValueConverterCollection>();
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
var logger = Mock.Of<ILogger>();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, Mock.Of<IMediaService>(), Mock.Of<IContentTypeBaseServiceProvider>());
var localLinkParser = new HtmlLocalLinkParser(umbracoContextAccessor);
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new RichTextPropertyEditor(Mock.Of<ILogger>(), Mock.Of<IMediaService>(), Mock.Of<IContentTypeBaseServiceProvider>(), Mock.Of<IUmbracoContextAccessor>())) { Id = 1 });
new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, localLinkParser, pastedImages)) { Id = 1 });
var publishedContentTypeFactory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), converters, dataTypeService);
@@ -21,6 +21,7 @@ using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using Umbraco.Web.Models.PublishedContent;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.PublishedContent
{
@@ -45,11 +46,14 @@ namespace Umbraco.Tests.PublishedContent
var mediaService = Mock.Of<IMediaService>();
var contentTypeBaseServiceProvider = Mock.Of<IContentTypeBaseServiceProvider>();
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var pastedImages = new RichTextEditorPastedImages(umbracoContextAccessor, logger, mediaService, contentTypeBaseServiceProvider);
var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor);
var dataTypeService = new TestObjects.TestDataTypeService(
new DataType(new VoidEditor(logger)) { Id = 1 },
new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 },
new DataType(new RichTextPropertyEditor(logger, mediaService, contentTypeBaseServiceProvider, umbracoContextAccessor)) { Id = 1002 },
new DataType(new RichTextPropertyEditor(logger, umbracoContextAccessor, imageSourceParser, linkParser, pastedImages)) { Id = 1002 },
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
new DataType(new TextboxPropertyEditor(logger)) { Id = 1004 },
new DataType(new MediaPickerPropertyEditor(logger)) { Id = 1005 });
@@ -14,6 +14,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -39,6 +40,23 @@ namespace Umbraco.Tests.Services
Composition.Register<IProfiler, TestProfiler>();
}
private DocumentRepository CreateDocumentRepository(IScopeProvider provider)
{
var accessor = (IScopeAccessor)provider;
var tRepository = new TemplateRepository(accessor, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository(accessor, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository(accessor, tRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
[Test]
public void Profiler()
{
@@ -163,12 +181,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
Stopwatch watch = Stopwatch.StartNew();
@@ -197,12 +210,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
Stopwatch watch = Stopwatch.StartNew();
@@ -229,12 +237,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor) provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
var contents = repository.GetMany();
@@ -264,12 +267,7 @@ namespace Umbraco.Tests.Services
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var tRepository = new TemplateRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, TestObjects.GetFileSystemsMock());
var tagRepo = new TagRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger);
var commonRepository = new ContentTypeCommonRepository((IScopeAccessor)provider, tRepository, AppCaches);
var languageRepository = new LanguageRepository((IScopeAccessor)provider, AppCaches.Disabled, Logger);
var ctRepository = new ContentTypeRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository((IScopeAccessor) provider, AppCaches.Disabled, Logger, ctRepository, tRepository, tagRepo, languageRepository);
var repository = CreateDocumentRepository(provider);
// Act
var contents = repository.GetMany();
@@ -448,6 +448,47 @@ namespace Umbraco.Tests.Services
Assert.That(content.HasIdentity, Is.False);
}
[Test]
public void Automatically_Track_Relations()
{
var mt = MockedContentTypes.CreateSimpleMediaType("testMediaType", "Test Media Type");
ServiceContext.MediaTypeService.Save(mt);
var m1 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1);
var m2 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1);
ServiceContext.MediaService.Save(m1);
ServiceContext.MediaService.Save(m2);
var ct = MockedContentTypes.CreateTextPageContentType("richTextTest");
ct.AllowedTemplates = Enumerable.Empty<ITemplate>();
ServiceContext.ContentTypeService.Save(ct);
var c1 = MockedContent.CreateTextpageContent(ct, "my content 1", -1);
ServiceContext.ContentService.Save(c1);
var c2 = MockedContent.CreateTextpageContent(ct, "my content 2", -1);
//'bodyText' is a property with a RTE property editor which we knows tracks relations
c2.Properties["bodyText"].SetValue(@"<p>
<img src='/media/12312.jpg' data-udi='umb://media/" + m1.Key.ToString("N") + @"' />
</p><p><img src='/media/234234.jpg' data-udi=""umb://media/" + m2.Key.ToString("N") + @""" />
</p>
<p>
<a href=""{locallink:umb://document/" + c1.Key.ToString("N") + @"}"">hello</a>
</p>");
ServiceContext.ContentService.Save(c2);
var relations = ServiceContext.RelationService.GetByParentId(c2.Id).ToList();
Assert.AreEqual(3, relations.Count);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[0].RelationType.Alias);
Assert.AreEqual(m1.Id, relations[0].ChildId);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedMediaAlias, relations[1].RelationType.Alias);
Assert.AreEqual(m2.Id, relations[1].ChildId);
Assert.AreEqual(Constants.Conventions.RelationTypes.RelatedDocumentAlias, relations[2].RelationType.Alias);
Assert.AreEqual(c1.Id, relations[2].ChildId);
}
[Test]
public void Can_Create_Content_Without_Explicitly_Set_User()
{
@@ -1778,7 +1819,7 @@ namespace Umbraco.Tests.Services
admin.StartContentIds = new[] {content1.Id};
ServiceContext.UserService.Save(admin);
ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, "test"));
ServiceContext.RelationService.Save(new RelationType("test", "test", false, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document));
Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test"));
ServiceContext.PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, new List<PublicAccessRule>
@@ -3166,7 +3207,12 @@ namespace Umbraco.Tests.Services
var commonRepository = new ContentTypeCommonRepository(accessor, templateRepository, AppCaches);
var languageRepository = new LanguageRepository(accessor, AppCaches.Disabled, Logger);
contentTypeRepository = new ContentTypeRepository(accessor, AppCaches.Disabled, Logger, commonRepository, languageRepository);
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository);
var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Logger);
var entityRepository = new EntityRepository(accessor);
var relationRepository = new RelationRepository(accessor, Logger, relationTypeRepository, entityRepository);
var propertyEditors = new Lazy<PropertyEditorCollection>(() => new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>())));
var dataValueReferences = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>());
var repository = new DocumentRepository(accessor, AppCaches.Disabled, Logger, contentTypeRepository, templateRepository, tagRepository, languageRepository, relationRepository, relationTypeRepository, propertyEditors, dataValueReferences);
return repository;
}
@@ -1,27 +1,247 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.Testing;
namespace Umbraco.Tests.Services
{
[TestFixture]
[Apartment(ApartmentState.STA)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerFixture)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
public class RelationServiceTests : TestWithSomeContentBase
{
[Test]
public void Get_Paged_Relations_By_Relation_Type()
{
//Create content
var createdContent = new List<IContent>();
var contentType = MockedContentTypes.CreateBasicContentType("blah");
ServiceContext.ContentTypeService.Save(contentType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedContent.CreateBasicContent(contentType);
ServiceContext.ContentService.Save(c1);
createdContent.Add(c1);
}
//Create media
var createdMedia = new List<IMedia>();
var imageType = MockedContentTypes.CreateImageMediaType("myImage");
ServiceContext.MediaTypeService.Save(imageType);
for (int i = 0; i < 10; i++)
{
var c1 = MockedMedia.CreateMediaImage(imageType, -1);
ServiceContext.MediaService.Save(c1);
createdMedia.Add(c1);
}
var relType = ServiceContext.RelationService.GetRelationTypeByAlias(Constants.Conventions.RelationTypes.RelatedMediaAlias);
// Relate content to media
foreach (var content in createdContent)
foreach (var media in createdMedia)
ServiceContext.RelationService.Relate(content.Id, media.Id, relType);
var paged = ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 0, 51, out var totalRecs).ToList();
Assert.AreEqual(100, totalRecs);
Assert.AreEqual(51, paged.Count);
//next page
paged.AddRange(ServiceContext.RelationService.GetPagedByRelationTypeId(relType.Id, 1, 51, out totalRecs));
Assert.AreEqual(100, totalRecs);
Assert.AreEqual(100, paged.Count);
Assert.IsTrue(createdContent.Select(x => x.Id).ContainsAll(paged.Select(x => x.ParentId)));
Assert.IsTrue(createdMedia.Select(x => x.Id).ContainsAll(paged.Select(x => x.ChildId)));
}
[Test]
public void Return_List_Of_Content_Items_Where_Media_Item_Referenced()
{
var mt = MockedContentTypes.CreateSimpleMediaType("testMediaType", "Test Media Type");
ServiceContext.MediaTypeService.Save(mt);
var m1 = MockedMedia.CreateSimpleMedia(mt, "hello 1", -1);
ServiceContext.MediaService.Save(m1);
var ct = MockedContentTypes.CreateTextPageContentType("richTextTest");
ct.AllowedTemplates = Enumerable.Empty<ITemplate>();
ServiceContext.ContentTypeService.Save(ct);
void createContentWithMediaRefs()
{
var content = MockedContent.CreateTextpageContent(ct, "my content 2", -1);
//'bodyText' is a property with a RTE property editor which we knows automatically tracks relations
content.Properties["bodyText"].SetValue(@"<p>
<img src='/media/12312.jpg' data-udi='umb://media/" + m1.Key.ToString("N") + @"' />
</p>");
ServiceContext.ContentService.Save(content);
}
for (var i = 0; i < 6; i++)
createContentWithMediaRefs(); //create 6 content items referencing the same media
var relations = ServiceContext.RelationService.GetByChildId(m1.Id, Constants.Conventions.RelationTypes.RelatedMediaAlias).ToList();
Assert.AreEqual(6, relations.Count);
var entities = ServiceContext.RelationService.GetParentEntitiesFromRelations(relations).ToList();
Assert.AreEqual(6, entities.Count);
}
[Test]
public void Can_Create_RelationType_Without_Name()
{
var rs = ServiceContext.RelationService;
var rt = new RelationType(Constants.ObjectTypes.Document, Constants.ObjectTypes.Document, "repeatedEventOccurence");
IRelationType rt = new RelationType("Test", "repeatedEventOccurence", false, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media);
Assert.DoesNotThrow(() => rs.Save(rt));
Assert.AreEqual(rt.Name, "repeatedEventOccurence");
//re-get
rt = ServiceContext.RelationService.GetRelationTypeById(rt.Id);
Assert.AreEqual("Test", rt.Name);
Assert.AreEqual("repeatedEventOccurence", rt.Alias);
Assert.AreEqual(false, rt.IsBidirectional);
Assert.AreEqual(Constants.ObjectTypes.Document, rt.ChildObjectType.Value);
Assert.AreEqual(Constants.ObjectTypes.Media, rt.ParentObjectType.Value);
}
[Test]
public void Create_Relation_Type_Without_Object_Types()
{
var rs = ServiceContext.RelationService;
IRelationType rt = new RelationType("repeatedEventOccurence", "repeatedEventOccurence", false, null, null);
Assert.DoesNotThrow(() => rs.Save(rt));
//re-get
rt = ServiceContext.RelationService.GetRelationTypeById(rt.Id);
Assert.IsNull(rt.ChildObjectType);
Assert.IsNull(rt.ParentObjectType);
}
[Test]
public void Relation_Returns_Parent_Child_Object_Types_When_Creating()
{
var r = CreateAndSaveRelation("Test", "test");
Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType);
Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType);
}
[Test]
public void Relation_Returns_Parent_Child_Object_Types_When_Getting()
{
var r = CreateAndSaveRelation("Test", "test");
// re-get
r = ServiceContext.RelationService.GetById(r.Id);
Assert.AreEqual(Constants.ObjectTypes.Document, r.ParentObjectType);
Assert.AreEqual(Constants.ObjectTypes.Media, r.ChildObjectType);
}
[Test]
public void Insert_Bulk_Relations()
{
var rs = ServiceContext.RelationService;
var newRelations = CreateRelations(10);
Assert.IsTrue(newRelations.All(x => !x.HasIdentity));
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.HasIdentity));
}
[Test]
public void Update_Bulk_Relations()
{
var rs = ServiceContext.RelationService;
var date = DateTime.Now.AddDays(-10);
var newRelations = CreateRelations(10);
foreach (var r in newRelations)
{
r.CreateDate = date;
r.UpdateDate = date;
}
//insert
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.UpdateDate == date));
var newDate = DateTime.Now.AddDays(-5);
foreach (var r in newRelations)
r.UpdateDate = newDate;
//update
ServiceContext.RelationService.Save(newRelations);
Assert.IsTrue(newRelations.All(x => x.UpdateDate == newDate));
}
private IRelation CreateAndSaveRelation(string name, string alias)
{
var rs = ServiceContext.RelationService;
var rt = new RelationType(name, alias, false, null, null);
rs.Save(rt);
var ct = MockedContentTypes.CreateBasicContentType();
ServiceContext.ContentTypeService.Save(ct);
var mt = MockedContentTypes.CreateImageMediaType("img");
ServiceContext.MediaTypeService.Save(mt);
var c1 = MockedContent.CreateBasicContent(ct);
var c2 = MockedMedia.CreateMediaImage(mt, -1);
ServiceContext.ContentService.Save(c1);
ServiceContext.MediaService.Save(c2);
var r = new Relation(c1.Id, c2.Id, rt);
ServiceContext.RelationService.Save(r);
return r;
}
/// <summary>
/// Creates a bunch of content/media items return relation objects for them (unsaved)
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
private IEnumerable<IRelation> CreateRelations(int count)
{
var rs = ServiceContext.RelationService;
var rtName = Guid.NewGuid().ToString();
var rt = new RelationType(rtName, rtName, false, null, null);
rs.Save(rt);
var ct = MockedContentTypes.CreateBasicContentType();
ServiceContext.ContentTypeService.Save(ct);
var mt = MockedContentTypes.CreateImageMediaType("img");
ServiceContext.MediaTypeService.Save(mt);
return Enumerable.Range(1, count).Select(index =>
{
var c1 = MockedContent.CreateBasicContent(ct);
var c2 = MockedMedia.CreateMediaImage(mt, -1);
ServiceContext.ContentService.Save(c1);
ServiceContext.MediaService.Save(c2);
return new Relation(c1.Id, c2.Id, rt);
}).ToList();
}
//TODO: Create a relation for entities of the wrong Entity Type (GUID) based on the Relation Type's defined parent/child object types
}
}
@@ -0,0 +1,120 @@
using Umbraco.Core.Logging;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Services;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web.Templates;
using Umbraco.Web;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.Routing;
using Umbraco.Tests.Testing.Objects;
using System.Web;
using System;
using System.Linq;
using Umbraco.Core.Models;
using Umbraco.Core;
using Umbraco.Web.PropertyEditors;
namespace Umbraco.Tests.Templates
{
[TestFixture]
public class HtmlImageSourceParserTests
{
[Test]
public void Returns_Udis_From_Data_Udi_Html_Attributes()
{
var input = @"<p>
<div>
<img src='/media/12312.jpg' data-udi='umb://media/D4B18427A1544721B09AC7692F35C264' />
</div>
</p><p><img src='/media/234234.jpg' data-udi=""umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"" /></p>";
var logger = Mock.Of<ILogger>();
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var result = imageSourceParser.FindUdisFromDataAttributes(input).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(Udi.Parse("umb://media/D4B18427A1544721B09AC7692F35C264"), result[0]);
Assert.AreEqual(Udi.Parse("umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"), result[1]);
}
[Test]
public void Remove_Image_Sources()
{
var logger = Mock.Of<ILogger>();
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var result = imageSourceParser.RemoveImageSources(@"<p>
<div>
<img src=""/media/12354/test.jpg"" />
</div></p>
<p>
<div><img src=""/media/987645/test.jpg"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>");
Assert.AreEqual(@"<p>
<div>
<img src=""/media/12354/test.jpg"" />
</div></p>
<p>
<div><img src="""" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>", result);
}
[Test]
public void Ensure_Image_Sources()
{
//setup a mock url provider which we'll use for testing
var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var media = new Mock<IPublishedContent>();
media.Setup(x => x.ContentType).Returns(mediaType);
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
mediaUrlProvider.Setup(x => x.GetMediaUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<string>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/media/1001/my-image.jpg"));
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var umbracoContextFactory = TestUmbracoContextFactory.Create(
mediaUrlProvider: mediaUrlProvider.Object,
umbracoContextAccessor: umbracoContextAccessor);
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
{
var mediaCache = Mock.Get(reference.UmbracoContext.Media);
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
var imageSourceParser = new HtmlImageSourceParser(umbracoContextAccessor);
var result = imageSourceParser.EnsureImageSources(@"<p>
<div>
<img src="""" />
</div></p>
<p>
<div><img src="""" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>
<p>
<div><img src=""?width=100"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>");
Assert.AreEqual(@"<p>
<div>
<img src="""" />
</div></p>
<p>
<div><img src=""/media/1001/my-image.jpg"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>
<p>
<div><img src=""/media/1001/my-image.jpg?width=100"" data-udi=""umb://media/81BB2036-034F-418B-B61F-C7160D68DCD4"" /></div>
</p>", result);
}
}
}
}
@@ -0,0 +1,94 @@
using Moq;
using NUnit.Framework;
using System;
using System.Linq;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Tests.Testing.Objects;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.Routing;
using Umbraco.Web.Templates;
namespace Umbraco.Tests.Templates
{
[TestFixture]
public class HtmlLocalLinkParserTests
{
[Test]
public void Returns_Udis_From_LocalLinks()
{
var input = @"<p>
<div>
<img src='/media/12312.jpg' data-udi='umb://media/D4B18427A1544721B09AC7692F35C264' />
<a href=""{locallink:umb://document/C093961595094900AAF9170DDE6AD442}"">hello</a>
</div>
</p><p><img src='/media/234234.jpg' data-udi=""umb://media-type/B726D735E4C446D58F703F3FBCFC97A5"" />
<a href=""{locallink:umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2}"">hello</a>
</p>";
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var parser = new HtmlLocalLinkParser(umbracoContextAccessor);
var result = parser.FindUdisFromLocalLinks(input).ToList();
Assert.AreEqual(2, result.Count);
Assert.AreEqual(Udi.Parse("umb://document/C093961595094900AAF9170DDE6AD442"), result[0]);
Assert.AreEqual(Udi.Parse("umb://document-type/2D692FCB070B4CDA92FB6883FDBFD6E2"), result[1]);
}
[TestCase("", "")]
[TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")]
//this one has an invalid char so won't match
[TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")]
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")]
public void ParseLocalLinks(string input, string result)
{
//setup a mock url provider which we'll use for testing
var contentUrlProvider = new Mock<IUrlProvider>();
contentUrlProvider
.Setup(x => x.GetUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/my-test-url"));
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = new Mock<IPublishedContent>();
publishedContent.Setup(x => x.Id).Returns(1234);
publishedContent.Setup(x => x.ContentType).Returns(contentType);
var mediaType = new PublishedContentType(777, "image", PublishedItemType.Media, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var media = new Mock<IPublishedContent>();
media.Setup(x => x.ContentType).Returns(mediaType);
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
mediaUrlProvider.Setup(x => x.GetMediaUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<string>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns(UrlInfo.Url("/media/1001/my-image.jpg"));
var umbracoContextAccessor = new TestUmbracoContextAccessor();
var umbracoContextFactory = TestUmbracoContextFactory.Create(
urlProvider: contentUrlProvider.Object,
mediaUrlProvider: mediaUrlProvider.Object,
umbracoContextAccessor: umbracoContextAccessor);
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
{
var contentCache = Mock.Get(reference.UmbracoContext.Content);
contentCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(publishedContent.Object);
contentCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent.Object);
var mediaCache = Mock.Get(reference.UmbracoContext.Media);
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
var linkParser = new HtmlLocalLinkParser(umbracoContextAccessor);
var output = linkParser.EnsureInternalLinks(input);
Assert.AreEqual(result, output);
}
}
}
}
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Scoping;
using Umbraco.Web;
using Umbraco.Web.PublishedCache.NuCache;
using Umbraco.Web.PublishedCache.NuCache.DataSource;
namespace Umbraco.Tests.Testing.Objects
{
internal class TestDataSource : IDataSource
{
public TestDataSource(params ContentNodeKit[] kits)
@@ -0,0 +1,49 @@
using Moq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.Testing.Objects
{
/// <summary>
/// Simplify creating test UmbracoContext's
/// </summary>
public class TestUmbracoContextFactory
{
public static IUmbracoContextFactory Create(IGlobalSettings globalSettings = null, IUrlProvider urlProvider = null,
IMediaUrlProvider mediaUrlProvider = null,
IUmbracoContextAccessor umbracoContextAccessor = null)
{
if (globalSettings == null) globalSettings = SettingsForTests.GenerateMockGlobalSettings();
if (urlProvider == null) urlProvider = Mock.Of<IUrlProvider>();
if (mediaUrlProvider == null) mediaUrlProvider = Mock.Of<IMediaUrlProvider>();
if (umbracoContextAccessor == null) umbracoContextAccessor = new TestUmbracoContextAccessor();
var contentCache = new Mock<IPublishedContentCache>();
var mediaCache = new Mock<IPublishedMediaCache>();
var snapshot = new Mock<IPublishedSnapshot>();
snapshot.Setup(x => x.Content).Returns(contentCache.Object);
snapshot.Setup(x => x.Media).Returns(mediaCache.Object);
var snapshotService = new Mock<IPublishedSnapshotService>();
snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(snapshot.Object);
var umbracoContextFactory = new UmbracoContextFactory(
umbracoContextAccessor,
snapshotService.Object,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(new[] { urlProvider }),
new MediaUrlProviderCollection(new[] { mediaUrlProvider }),
Mock.Of<IUserService>());
return umbracoContextFactory;
}
}
}
@@ -41,6 +41,8 @@ using Umbraco.Web.Composing.CompositionExtensions;
using Umbraco.Web.Sections;
using Current = Umbraco.Core.Composing.Current;
using FileSystems = Umbraco.Core.IO.FileSystems;
using Umbraco.Web.Templates;
using Umbraco.Web.PropertyEditors;
namespace Umbraco.Tests.Testing
{
@@ -215,6 +217,9 @@ namespace Umbraco.Tests.Testing
Composition.RegisterUnique(_ => Umbraco.Web.Composing.Current.UmbracoContextAccessor);
Composition.RegisterUnique<IPublishedRouter, PublishedRouter>();
Composition.WithCollectionBuilder<ContentFinderCollectionBuilder>();
Composition.DataValueReferenceFactories();
Composition.RegisterUnique<IContentLastChanceFinder, TestLastChanceFinder>();
Composition.RegisterUnique<IVariationContextAccessor, TestVariationContextAccessor>();
Composition.RegisterUnique<IPublishedSnapshotAccessor, TestPublishedSnapshotAccessor>();
@@ -230,6 +235,11 @@ namespace Umbraco.Tests.Testing
.Append<TranslationSection>();
Composition.RegisterUnique<ISectionService, SectionService>();
Composition.RegisterUnique<HtmlLocalLinkParser>();
Composition.RegisterUnique<HtmlUrlParser>();
Composition.RegisterUnique<HtmlImageSourceParser>();
Composition.RegisterUnique<RichTextEditorPastedImages>();
}
protected virtual void ComposeMisc()
+4
View File
@@ -144,6 +144,7 @@
<Compile Include="Models\VariationTests.cs" />
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
@@ -163,6 +164,7 @@
<Compile Include="Services\MemberGroupServiceTests.cs" />
<Compile Include="Services\MediaTypeServiceTests.cs" />
<Compile Include="Services\PropertyValidationServiceTests.cs" />
<Compile Include="Templates\HtmlLocalLinkParserTests.cs" />
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
<Compile Include="Testing\Objects\TestDataSource.cs" />
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
@@ -216,6 +218,7 @@
<Compile Include="Testing\Objects\Accessors\TestVariationContextAccessor.cs" />
<Compile Include="Testing\ContentBaseExtensions.cs" />
<Compile Include="Testing\Objects\Accessors\TestDefaultCultureAccessor.cs" />
<Compile Include="Testing\Objects\TestUmbracoContextFactory.cs" />
<Compile Include="Testing\TestDatabase.cs" />
<Compile Include="Testing\TestingTests\NUnitTests.cs" />
<Compile Include="Persistence\LocksTests.cs" />
@@ -257,6 +260,7 @@
<Compile Include="Web\Controllers\UsersControllerTests.cs" />
<Compile Include="Web\HealthChecks\HealthCheckResultsTests.cs" />
<Compile Include="Web\HttpCookieExtensionsTests.cs" />
<Compile Include="Templates\HtmlImageSourceParserTests.cs" />
<Compile Include="Web\Mvc\HtmlStringUtilitiesTests.cs" />
<Compile Include="Web\ModelStateExtensionsTests.cs" />
<Compile Include="Web\Mvc\RenderIndexActionSelectorAttributeTests.cs" />
@@ -1,6 +1,4 @@
using System;
using System.Linq;
using System.Web;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -9,20 +7,16 @@ using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing.Objects.Accessors;
using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
using Umbraco.Web.Templates;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
namespace Umbraco.Tests.Web
{
[TestFixture]
public class TemplateUtilitiesTests
{
@@ -59,67 +53,6 @@ namespace Umbraco.Tests.Web
Current.Reset();
}
[TestCase("", "")]
[TestCase("hello href=\"{localLink:1234}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
[TestCase("hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")]
//this one has an invalid char so won't match
[TestCase("hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")]
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"#\" world ")]
public void ParseLocalLinks(string input, string result)
{
var serviceCtxMock = new TestObjects(null).GetServiceContextMock();
//setup a mock entity service from the service context to return an integer for a GUID
var entityService = Mock.Get(serviceCtxMock.EntityService);
//entityService.Setup(x => x.GetId(It.IsAny<Guid>(), It.IsAny<UmbracoObjectTypes>()))
// .Returns((Guid id, UmbracoObjectTypes objType) =>
// {
// return Attempt.Succeed(1234);
// });
//setup a mock url provider which we'll use for testing
var testUrlProvider = new Mock<IUrlProvider>();
testUrlProvider
.Setup(x => x.GetUrl(It.IsAny<UmbracoContext>(), It.IsAny<IPublishedContent>(), It.IsAny<UrlMode>(), It.IsAny<string>(), It.IsAny<Uri>()))
.Returns((UmbracoContext umbCtx, IPublishedContent content, UrlMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url"));
var globalSettings = SettingsForTests.GenerateMockGlobalSettings();
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var publishedContent = Mock.Of<IPublishedContent>();
Mock.Get(publishedContent).Setup(x => x.Id).Returns(1234);
Mock.Get(publishedContent).Setup(x => x.ContentType).Returns(contentType);
var contentCache = Mock.Of<IPublishedContentCache>();
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<int>())).Returns(publishedContent);
Mock.Get(contentCache).Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent);
var snapshot = Mock.Of<IPublishedSnapshot>();
Mock.Get(snapshot).Setup(x => x.Content).Returns(contentCache);
var snapshotService = Mock.Of<IPublishedSnapshotService>();
Mock.Get(snapshotService).Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(snapshot);
var media = Mock.Of<IPublishedContent>();
Mock.Get(media).Setup(x => x.Url).Returns("/media/1001/my-image.jpg");
var mediaCache = Mock.Of<IPublishedMediaCache>();
Mock.Get(mediaCache).Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media);
var umbracoContextFactory = new UmbracoContextFactory(
Umbraco.Web.Composing.Current.UmbracoContextAccessor,
snapshotService,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "Auto")),
globalSettings,
new UrlProviderCollection(new[] { testUrlProvider.Object }),
new MediaUrlProviderCollection(Enumerable.Empty<IMediaUrlProvider>()),
Mock.Of<IUserService>());
using (var reference = umbracoContextFactory.EnsureUmbracoContext(Mock.Of<HttpContextBase>()))
{
var output = TemplateUtilities.ParseInternalLinks(input, reference.UmbracoContext.UrlProvider, mediaCache);
Assert.AreEqual(result, output);
}
}
}
}
@@ -791,6 +791,7 @@
$scope.content.variants[i].expireDate = model.variants[i].expireDate;
$scope.content.variants[i].releaseDateFormatted = model.variants[i].releaseDateFormatted;
$scope.content.variants[i].expireDateFormatted = model.variants[i].expireDateFormatted;
$scope.content.variants[i].save = model.variants[i].save;
}
model.submitButtonState = "busy";
@@ -1,13 +1,30 @@
(function () {
'use strict';
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper) {
function MediaNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService, mediaHelper, mediaResource, $q) {
function link(scope, element, attrs, ctrl) {
var evts = [];
scope.allowChangeMediaType = false;
scope.loading = true;
scope.changeContentPageNumber = changeContentPageNumber;
scope.contentOptions = {};
scope.contentOptions.entityType = "DOCUMENT";
scope.hasContentReferences = false;
scope.changeMediaPageNumber = changeMediaPageNumber;
scope.mediaOptions = {};
scope.mediaOptions.entityType = "MEDIA";
scope.hasMediaReferences = false;
scope.changeMemberPageNumber = changeMemberPageNumber;
scope.memberOptions = {};
scope.memberOptions.entityType = "MEMBER";
scope.hasMemberReferences = false;
function onInit() {
@@ -94,6 +111,45 @@
setMediaExtension();
});
function changeContentPageNumber(pageNumber) {
scope.contentOptions.pageNumber = pageNumber;
loadContentRelations();
}
function changeMediaPageNumber(pageNumber) {
scope.mediaOptions.pageNumber = pageNumber;
loadMediaRelations();
}
function changeMemberPageNumber(pageNumber) {
scope.memberOptions.pageNumber = pageNumber;
loadMemberRelations();
}
function loadContentRelations() {
return mediaResource.getPagedReferences(scope.node.id, scope.contentOptions)
.then(function (data) {
scope.contentReferences = data;
scope.hasContentReferences = data.items.length > 0;
});
}
function loadMediaRelations() {
return mediaResource.getPagedReferences(scope.node.id, scope.mediaOptions)
.then(function (data) {
scope.mediaReferences = data;
scope.hasMediaReferences = data.items.length > 0;
});
}
function loadMemberRelations() {
return mediaResource.getPagedReferences(scope.node.id, scope.memberOptions)
.then(function (data) {
scope.memberReferences = data;
scope.hasMemberReferences = data.items.length > 0;
});
}
//ensure to unregister from all events!
scope.$on('$destroy', function () {
for (var e in evts) {
@@ -102,6 +158,18 @@
});
onInit();
// load media type references when the 'info' tab is first activated/switched to
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
if (args.alias === "umbInfo") {
$q.all([loadContentRelations(), loadMediaRelations(), loadMemberRelations()]).then(function () {
scope.loading = false;
});
}
});
}));
}
var directive = {
@@ -552,6 +552,36 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"Search",
args)),
'Failed to retrieve media items for search: ' + query);
},
getPagedReferences: function (id, options) {
var defaults = {
pageSize: 25,
pageNumber: 1,
entityType: "DOCUMENT"
};
if (options === undefined) {
options = {};
}
//overwrite the defaults if there are any specified
angular.extend(defaults, options);
//now copy back to the options we will use
options = defaults;
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"mediaApiBaseUrl",
"GetPagedReferences",
{
id: id,
entityType: options.entityType,
pageNumber: options.pageNumber,
pageSize: options.pageSize
}
)),
"Failed to retrieve usages for media of id " + id);
}
};

Some files were not shown because too many files have changed in this diff Show More