Merge branch 'v8/contrib' into pr/5626
This commit is contained in:
+10
-6
@@ -13,7 +13,7 @@
|
||||
*.png binary
|
||||
*.gif binary
|
||||
|
||||
*.cs text=auto diff=csharp
|
||||
*.cs text=auto diff=csharp
|
||||
*.vb text=auto
|
||||
*.c text=auto
|
||||
*.cpp text=auto
|
||||
@@ -41,9 +41,13 @@
|
||||
*.fs text=auto
|
||||
*.fsx text=auto
|
||||
*.hs text=auto
|
||||
*.json text=auto
|
||||
*.xml text=auto
|
||||
|
||||
*.csproj text=auto merge=union
|
||||
*.vbproj text=auto merge=union
|
||||
*.fsproj text=auto merge=union
|
||||
*.dbproj text=auto merge=union
|
||||
*.sln text=auto eol=crlf merge=union
|
||||
*.csproj text=auto merge=union
|
||||
*.vbproj text=auto merge=union
|
||||
*.fsproj text=auto merge=union
|
||||
*.dbproj text=auto merge=union
|
||||
*.sln text=auto eol=crlf merge=union
|
||||
|
||||
*.gitattributes text=auto
|
||||
|
||||
@@ -60,10 +60,10 @@ Great question! The short version goes like this:
|
||||
|
||||

|
||||
|
||||
* **Switch to the correct branch** - switch to the v8-dev branch
|
||||
* **Switch to the correct branch** - switch to the `v8/contrib` branch
|
||||
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
|
||||
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
|
||||
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first.
|
||||
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first.
|
||||
* **Push** - great, now you can push the changes up to your fork on GitHub
|
||||
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
|
||||
|
||||
@@ -158,7 +158,7 @@ To find the general areas for something you're looking to fix or improve, have a
|
||||
|
||||
### Which branch should I target for my contributions?
|
||||
|
||||
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
|
||||
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
|
||||
|
||||
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
|
||||
|
||||
@@ -184,10 +184,10 @@ Then when you want to get the changes from the main repository:
|
||||
|
||||
```
|
||||
git fetch upstream
|
||||
git rebase upstream/v8/dev
|
||||
git rebase upstream/v8/contrib
|
||||
```
|
||||
|
||||
In this command we're syncing with the `v8/dev` branch, but you can of course choose another one if needed.
|
||||
In this command we're syncing with the `v8/contrib` branch, but you can of course choose another one if needed.
|
||||
|
||||
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
|
||||
# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
|
||||
|
||||
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 24 KiB |
@@ -28,7 +28,7 @@
|
||||
<dependency id="ClientDependency" version="[1.9.8,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.9.3,1.999999)" />
|
||||
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
|
||||
<dependency id="Examine" version="[1.0.1,1.999999)" />
|
||||
<dependency id="Examine" version="[1.0.2,1.999999)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.8.14,1.999999)" />
|
||||
<dependency id="ImageProcessor" version="[2.7.0.100,2.999999)" />
|
||||
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />
|
||||
|
||||
+2
-2
@@ -18,5 +18,5 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("8.0.0")]
|
||||
|
||||
// these are FYI and changed automatically
|
||||
[assembly: AssemblyFileVersion("8.6.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.6.0")]
|
||||
[assembly: AssemblyFileVersion("8.7.0")]
|
||||
[assembly: AssemblyInformationalVersion("8.7.0")]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Composing
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(componentType, ex, "Error while terminating component {ComponentType}.", componentType.FullName);
|
||||
_logger.Error<ComponentCollection>(ex, "Error while terminating component {ComponentType}.", componentType.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -41,6 +43,21 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string Path = "Umbraco.Core.Path";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the css directory (/css by default).
|
||||
/// </summary>
|
||||
public const string CssPath = "umbracoCssPath";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the scripts directory (/scripts by default).
|
||||
/// </summary>
|
||||
public const string ScriptsPath = "umbracoScriptsPath";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to media directory (/media by default).
|
||||
/// </summary>
|
||||
public const string MediaPath = "umbracoMediaPath";
|
||||
|
||||
/// <summary>
|
||||
/// The reserved urls from web.config.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,121 +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 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>
|
||||
@@ -135,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();
|
||||
@@ -161,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
|
||||
{
|
||||
@@ -178,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
|
||||
@@ -190,6 +333,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (throwIfInvalid)
|
||||
throw new NotSupportedException($"Segment \"{segment}\" is invalid because segment variation is disabled.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public static string MacroPartials => MvcViews + "/MacroPartials/";
|
||||
|
||||
public static string Media => IOHelper.ReturnPath("umbracoMediaPath", "~/media");
|
||||
public static string Media => IOHelper.ReturnPath(Constants.AppSettings.MediaPath, "~/media");
|
||||
|
||||
public static string Scripts => IOHelper.ReturnPath("umbracoScriptsPath", "~/scripts");
|
||||
public static string Scripts => IOHelper.ReturnPath(Constants.AppSettings.ScriptsPath, "~/scripts");
|
||||
|
||||
public static string Css => IOHelper.ReturnPath("umbracoCssPath", "~/css");
|
||||
public static string Css => IOHelper.ReturnPath(Constants.AppSettings.CssPath, "~/css");
|
||||
|
||||
public static string Umbraco => IOHelper.ReturnPath("umbracoPath", "~/umbraco");
|
||||
public static string Umbraco => IOHelper.ReturnPath(Constants.AppSettings.Path, "~/umbraco");
|
||||
|
||||
public static string Packages => Data + "/packages";
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal static class MediaTypeExtensions
|
||||
{
|
||||
internal static bool IsSystemMediaType(this IMediaType mediaType) =>
|
||||
mediaType.Alias == Constants.Conventions.MediaTypes.File
|
||||
|| mediaType.Alias == Constants.Conventions.MediaTypes.Folder
|
||||
|| mediaType.Alias == Constants.Conventions.MediaTypes.Image;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -26,5 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <param name="contentPath"></param>
|
||||
/// <returns></returns>
|
||||
bool HasContainerInPath(string contentPath);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
|
||||
/// </summary>
|
||||
bool HasContentNodes(int id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,6 +1323,17 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT
|
||||
return Database.ExecuteScalar<int>(sql) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
|
||||
/// </summary>
|
||||
public bool HasContentNodes(int id)
|
||||
{
|
||||
var sql = new Sql(
|
||||
$"SELECT CASE WHEN EXISTS (SELECT * FROM {Constants.DatabaseSchema.Tables.Content} WHERE contentTypeId = @id) THEN 1 ELSE 0 END",
|
||||
new { id });
|
||||
return Database.ExecuteScalar<int>(sql) == 1;
|
||||
}
|
||||
|
||||
protected override IEnumerable<string> GetDeleteClauses()
|
||||
{
|
||||
// in theory, services should have ensured that content items of the given content type
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Scoping
|
||||
/// Provides scopes.
|
||||
/// </summary>
|
||||
public interface IScopeProvider
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an ambient scope.
|
||||
/// </summary>
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace Umbraco.Core.Services
|
||||
|
||||
int Count();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
|
||||
/// </summary>
|
||||
bool HasContentNodes(int id);
|
||||
|
||||
IEnumerable<TItem> GetAll(params int[] ids);
|
||||
IEnumerable<TItem> GetAll(IEnumerable<Guid> ids);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -372,6 +372,15 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasContentNodes(int id)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(ReadLockIds);
|
||||
return Repository.HasContentNodes(id);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Save
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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" />
|
||||
@@ -277,10 +284,15 @@
|
||||
<Compile Include="Models\Entities\MediaEntitySlim.cs" />
|
||||
<Compile Include="Models\PagedResult.cs" />
|
||||
<Compile Include="Models\Entities\MemberEntitySlim.cs" />
|
||||
<Compile Include="Models\MediaTypeExtensions.cs" />
|
||||
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
|
||||
<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" />
|
||||
@@ -402,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" />
|
||||
@@ -733,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" />
|
||||
@@ -1530,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" />
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- note: NuGet deals with transitive references now -->
|
||||
<PackageReference Include="Examine" Version="1.0.1" />
|
||||
<PackageReference Include="Examine" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub">
|
||||
<Version>1.0.0-beta2-19324-01</Version>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@@ -112,4 +112,4 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -22,6 +22,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
|
||||
{
|
||||
var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled();
|
||||
|
||||
|
||||
composition.Configs.Add<IModelsBuilderConfig>(() => new ModelsBuilderConfig());
|
||||
|
||||
if (isLegacyModelsBuilderInstalled)
|
||||
{
|
||||
ComposeForLegacyModelsBuilder(composition);
|
||||
@@ -30,7 +33,7 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
|
||||
|
||||
composition.Components().Append<ModelsBuilderComponent>();
|
||||
composition.Register<UmbracoServices>(Lifetime.Singleton);
|
||||
composition.Configs.Add<IModelsBuilderConfig>(() => new ModelsBuilderConfig());
|
||||
|
||||
composition.RegisterUnique<ModelsGenerator>();
|
||||
composition.RegisterUnique<LiveModelsProvider>();
|
||||
composition.RegisterUnique<OutOfDateModelsStatus>();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Web.Configuration;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -12,6 +13,13 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
/// </summary>
|
||||
public class ModelsBuilderConfig : IModelsBuilderConfig
|
||||
{
|
||||
private const string Prefix = "Umbraco.ModelsBuilder.";
|
||||
private object _modelsModelLock;
|
||||
private bool _modelsModelConfigured;
|
||||
private ModelsMode _modelsMode;
|
||||
private object _flagOutOfDateModelsLock;
|
||||
private bool _flagOutOfDateModelsConfigured;
|
||||
private bool _flagOutOfDateModels;
|
||||
public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
|
||||
public const string DefaultModelsDirectory = "~/App_Data/Models";
|
||||
|
||||
@@ -20,11 +28,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
/// </summary>
|
||||
public ModelsBuilderConfig()
|
||||
{
|
||||
const string prefix = "Umbraco.ModelsBuilder.";
|
||||
|
||||
// giant kill switch, default: false
|
||||
// must be explicitely set to true for anything else to happen
|
||||
Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true";
|
||||
Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true";
|
||||
|
||||
// ensure defaults are initialized for tests
|
||||
ModelsNamespace = DefaultModelsNamespace;
|
||||
@@ -34,44 +40,19 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
// stop here, everything is false
|
||||
if (!Enable) return;
|
||||
|
||||
// mode
|
||||
var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"];
|
||||
if (!string.IsNullOrWhiteSpace(modelsMode))
|
||||
{
|
||||
switch (modelsMode)
|
||||
{
|
||||
case nameof(ModelsMode.Nothing):
|
||||
ModelsMode = ModelsMode.Nothing;
|
||||
break;
|
||||
case nameof(ModelsMode.PureLive):
|
||||
ModelsMode = ModelsMode.PureLive;
|
||||
break;
|
||||
case nameof(ModelsMode.AppData):
|
||||
ModelsMode = ModelsMode.AppData;
|
||||
break;
|
||||
case nameof(ModelsMode.LiveAppData):
|
||||
ModelsMode = ModelsMode.LiveAppData;
|
||||
break;
|
||||
default:
|
||||
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode."
|
||||
+ " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
|
||||
}
|
||||
}
|
||||
|
||||
// default: false
|
||||
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
|
||||
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[Prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
|
||||
|
||||
// default: true
|
||||
EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false");
|
||||
FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false");
|
||||
EnableFactory = !ConfigurationManager.AppSettings[Prefix + "EnableFactory"].InvariantEquals("false");
|
||||
|
||||
// default: initialized above with DefaultModelsNamespace const
|
||||
var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"];
|
||||
var value = ConfigurationManager.AppSettings[Prefix + "ModelsNamespace"];
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
ModelsNamespace = value;
|
||||
|
||||
// default: initialized above with DefaultModelsDirectory const
|
||||
value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"];
|
||||
value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"];
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
var root = IOHelper.MapPath("~/");
|
||||
@@ -83,18 +64,14 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
}
|
||||
|
||||
// default: 0
|
||||
value = ConfigurationManager.AppSettings[prefix + "DebugLevel"];
|
||||
value = ConfigurationManager.AppSettings[Prefix + "DebugLevel"];
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
int debugLevel;
|
||||
if (!int.TryParse(value, out debugLevel))
|
||||
if (!int.TryParse(value, out var debugLevel))
|
||||
throw new ConfigurationErrorsException($"Invalid debug level \"{value}\".");
|
||||
DebugLevel = debugLevel;
|
||||
}
|
||||
|
||||
// not flagging if not generating, or live (incl. pure)
|
||||
if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive())
|
||||
FlagOutOfDateModels = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,11 +88,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
int debugLevel = 0)
|
||||
{
|
||||
Enable = enable;
|
||||
ModelsMode = modelsMode;
|
||||
_modelsMode = modelsMode;
|
||||
|
||||
ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace;
|
||||
EnableFactory = enableFactory;
|
||||
FlagOutOfDateModels = flagOutOfDateModels;
|
||||
_flagOutOfDateModels = flagOutOfDateModels;
|
||||
ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory;
|
||||
AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory;
|
||||
DebugLevel = debugLevel;
|
||||
@@ -164,7 +141,26 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
/// <summary>
|
||||
/// Gets the models mode.
|
||||
/// </summary>
|
||||
public ModelsMode ModelsMode { get; }
|
||||
public ModelsMode ModelsMode =>
|
||||
LazyInitializer.EnsureInitialized(ref _modelsMode, ref _modelsModelConfigured, ref _modelsModelLock, () =>
|
||||
{
|
||||
// mode
|
||||
var modelsMode = ConfigurationManager.AppSettings[Prefix + "ModelsMode"];
|
||||
if (string.IsNullOrWhiteSpace(modelsMode)) return ModelsMode.Nothing; //default
|
||||
switch (modelsMode)
|
||||
{
|
||||
case nameof(ModelsMode.Nothing):
|
||||
return ModelsMode.Nothing;
|
||||
case nameof(ModelsMode.PureLive):
|
||||
return ModelsMode.PureLive;
|
||||
case nameof(ModelsMode.AppData):
|
||||
return ModelsMode.AppData;
|
||||
case nameof(ModelsMode.LiveAppData):
|
||||
return ModelsMode.LiveAppData;
|
||||
default:
|
||||
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether system.web/compilation/@debug is true.
|
||||
@@ -196,7 +192,17 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
|
||||
/// <remarks>Models become out-of-date when data types or content types are updated. When this
|
||||
/// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are
|
||||
/// generated through the dashboard, the files is cleared. Default value is <c>false</c>.</remarks>
|
||||
public bool FlagOutOfDateModels { get; }
|
||||
public bool FlagOutOfDateModels
|
||||
=> LazyInitializer.EnsureInitialized(ref _flagOutOfDateModels, ref _flagOutOfDateModelsConfigured, ref _flagOutOfDateModelsLock, () =>
|
||||
{
|
||||
var flagOutOfDateModels = !ConfigurationManager.AppSettings[Prefix + "FlagOutOfDateModels"].InvariantEquals("false");
|
||||
if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive())
|
||||
{
|
||||
flagOutOfDateModels = false;
|
||||
}
|
||||
|
||||
return flagOutOfDateModels;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Gets the models directory.
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
/// </summary>
|
||||
/// <remarks>And therefore it should not be generated.</remarks>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class ImplementPropertyTypeAttribute : Attribute
|
||||
public class ImplementPropertyTypeAttribute : Attribute
|
||||
{
|
||||
public ImplementPropertyTypeAttribute(string alias)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Umbraco.Web
|
||||
/// <summary>
|
||||
/// Gets the value of a property.
|
||||
/// </summary>
|
||||
public static TValue Value<TModel, TValue>(this TModel model, Expression<Func<TModel, TValue>> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default)
|
||||
public static TValue ValueFor<TModel, TValue>(this TModel model, Expression<Func<TModel, TValue>> property, string culture = null, string segment = null, Fallback fallback = default, TValue defaultValue = default)
|
||||
where TModel : IPublishedElement
|
||||
{
|
||||
var alias = GetAlias(model, property);
|
||||
@@ -45,7 +45,7 @@ namespace Umbraco.Web
|
||||
var attribute = member.GetCustomAttribute<ImplementPropertyTypeAttribute>();
|
||||
if (attribute == null)
|
||||
throw new InvalidOperationException("Property is not marked with ImplementPropertyType attribute.");
|
||||
|
||||
|
||||
return attribute.Alias;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Umbraco.TestData")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Umbraco.TestData")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("fb5676ed-7a69-492c-b802-e7b24144c0fc")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.TestData
|
||||
{
|
||||
public class SegmentTestController : SurfaceController
|
||||
{
|
||||
|
||||
public ActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias)
|
||||
{
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return HttpNotFound();
|
||||
|
||||
var ct = Services.ContentTypeService.Get(alias);
|
||||
if (ct == null)
|
||||
return Content($"No document type found by alias {alias}");
|
||||
|
||||
var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
if (propType == null)
|
||||
return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}");
|
||||
|
||||
if (ct.Variations.VariesBySegment())
|
||||
return Content($"The document type {alias} already allows segments, nothing has been changed");
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
public ActionResult DisableDocTypeSegments(string alias)
|
||||
{
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return HttpNotFound();
|
||||
|
||||
var ct = Services.ContentTypeService.Get(alias);
|
||||
if (ct == null)
|
||||
return Content($"No document type found by alias {alias}");
|
||||
|
||||
if (!ct.VariesBySegment())
|
||||
return Content($"The document type {alias} does not allow segments, nothing has been changed");
|
||||
|
||||
ct.SetVariesBy(ContentVariation.Segment, false);
|
||||
|
||||
Services.ContentTypeService.Save(ct);
|
||||
return Content($"The document type {alias} no longer allows segments");
|
||||
}
|
||||
|
||||
public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string culture = null)
|
||||
{
|
||||
var content = Services.ContentService.GetById(contentId);
|
||||
if (content == null)
|
||||
return Content($"No content found by id {contentId}");
|
||||
|
||||
if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias))
|
||||
return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}");
|
||||
|
||||
if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace())
|
||||
return Content($"The content by id {contentId} varies by culture but no culture was specified");
|
||||
|
||||
if (value.IsNullOrWhiteSpace())
|
||||
return Content("'value' cannot be null");
|
||||
|
||||
if (segment.IsNullOrWhiteSpace())
|
||||
return Content("'segment' cannot be null");
|
||||
|
||||
content.SetValue(propertyAlias, value, culture, segment);
|
||||
Services.ContentService.Save(content);
|
||||
|
||||
return Content($"Segment value has been set on content {contentId} for property {propertyAlias}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FB5676ED-7A69-492C-B802-E7B24144C0FC}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Umbraco.TestData</RootNamespace>
|
||||
<AssemblyName>Umbraco.TestData</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="SegmentTestController.cs" />
|
||||
<Compile Include="UmbracoTestDataController.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="readme.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
|
||||
<Project>{31785bc3-256c-4613-b2f5-a1b0bdded8c1}</Project>
|
||||
<Name>Umbraco.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
|
||||
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
|
||||
<Name>Umbraco.Web</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Bogus">
|
||||
<Version>28.4.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNet.Mvc">
|
||||
<Version>5.2.7</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,288 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using System.Web.Mvc;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Configuration;
|
||||
using Bogus;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.TestData
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates test data
|
||||
/// </summary>
|
||||
public class UmbracoTestDataController : SurfaceController
|
||||
{
|
||||
private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE";
|
||||
private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker";
|
||||
private const string TextDataTypeName = "UmbracoTestDataContent.Text";
|
||||
private const string TestDataContentTypeAlias = "umbTestDataContent";
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
|
||||
public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper) : base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger, umbracoHelper)
|
||||
{
|
||||
_scopeProvider = scopeProvider;
|
||||
_propertyEditors = propertyEditors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a content and associated media tree (hierarchy)
|
||||
/// </summary>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="depth"></param>
|
||||
/// <param name="locale"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two
|
||||
/// </remarks>
|
||||
public ActionResult CreateTree(int count, int depth, string locale = "en")
|
||||
{
|
||||
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
|
||||
return HttpNotFound();
|
||||
|
||||
if (!Validate(count, depth, out var message, out var perLevel))
|
||||
throw new InvalidOperationException(message);
|
||||
|
||||
var faker = new Faker(locale);
|
||||
var company = faker.Company.CompanyName();
|
||||
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
var imageIds = CreateMediaTree(company, faker, count, depth).ToList();
|
||||
var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList();
|
||||
|
||||
Services.ContentService.SaveAndPublishBranch(root, true);
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
|
||||
return Content("Done");
|
||||
}
|
||||
|
||||
private bool Validate(int count, int depth, out string message, out int perLevel)
|
||||
{
|
||||
perLevel = 0;
|
||||
message = null;
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
message = "Count must be more than 0";
|
||||
return false;
|
||||
}
|
||||
|
||||
perLevel = count / depth;
|
||||
if (perLevel < 1)
|
||||
{
|
||||
message = "Count not high enough for specified for number of levels required";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility to create a tree hierarchy
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="depth"></param>
|
||||
/// <param name="create">
|
||||
/// A callback that returns a tuple of Content and another callback to produce a Container.
|
||||
/// For media, a container will be another folder, for content the container will be the Content itself.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<Udi> CreateHierarchy<T>(
|
||||
T parent, int count, int depth,
|
||||
Func<T, (T content, Func<T> container)> create)
|
||||
where T: class, IContentBase
|
||||
{
|
||||
yield return parent.GetUdi();
|
||||
|
||||
// This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree.
|
||||
var totalDescendants = count - 1;
|
||||
var perLevel = Math.Ceiling(totalDescendants / (double)depth);
|
||||
var perBranch = Math.Ceiling(perLevel / depth);
|
||||
|
||||
var tracked = new Stack<(T parent, int childCount)>();
|
||||
|
||||
var currChildCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var created = create(parent);
|
||||
var contentItem = created.content;
|
||||
|
||||
yield return contentItem.GetUdi();
|
||||
|
||||
currChildCount++;
|
||||
|
||||
if (currChildCount == perBranch)
|
||||
{
|
||||
// move back up...
|
||||
|
||||
var prev = tracked.Pop();
|
||||
|
||||
// restore child count
|
||||
currChildCount = prev.childCount;
|
||||
// restore the parent
|
||||
parent = prev.parent;
|
||||
|
||||
}
|
||||
else if (contentItem.Level < depth)
|
||||
{
|
||||
// track the current parent and it's current child count
|
||||
tracked.Push((parent, currChildCount));
|
||||
|
||||
// not at max depth, create below
|
||||
parent = created.container();
|
||||
|
||||
currChildCount = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the media tree hiearachy
|
||||
/// </summary>
|
||||
/// <param name="company"></param>
|
||||
/// <param name="faker"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="depth"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<Udi> CreateMediaTree(string company, Faker faker, int count, int depth)
|
||||
{
|
||||
var parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder);
|
||||
|
||||
return CreateHierarchy(parent, count, depth, currParent =>
|
||||
{
|
||||
var imageUrl = faker.Image.PicsumUrl();
|
||||
|
||||
// we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like:
|
||||
// https://picsum.photos/640/480/?image=106
|
||||
// and due to the way that we detect images there must be an extension so we'll change it to
|
||||
// https://picsum.photos/640/480/?image=106&ext=.jpg
|
||||
// which will trick our app into parsing this and thinking it's an image ... which it is so that's good.
|
||||
// if we don't do this we don't get thumbnails in the back office.
|
||||
imageUrl += "&ext=.jpg";
|
||||
|
||||
var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image);
|
||||
media.SetValue(Constants.Conventions.Media.File, imageUrl);
|
||||
Services.MediaService.Save(media);
|
||||
return (media, () =>
|
||||
{
|
||||
// create a folder to contain child media
|
||||
var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder);
|
||||
return container;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the content tree hiearachy
|
||||
/// </summary>
|
||||
/// <param name="company"></param>
|
||||
/// <param name="faker"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <param name="depth"></param>
|
||||
/// <param name="imageIds"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<Udi> CreateContentTree(string company, Faker faker, int count, int depth, List<Udi> imageIds, out IContent root)
|
||||
{
|
||||
var random = new Random(company.GetHashCode());
|
||||
|
||||
var docType = GetOrCreateContentType();
|
||||
|
||||
var parent = Services.ContentService.Create(company, -1, docType.Alias);
|
||||
parent.SetValue("review", faker.Rant.Review());
|
||||
parent.SetValue("desc", company);
|
||||
parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
|
||||
Services.ContentService.Save(parent);
|
||||
|
||||
root = parent;
|
||||
|
||||
return CreateHierarchy(parent, count, depth, currParent =>
|
||||
{
|
||||
var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
|
||||
content.SetValue("review", faker.Rant.Review());
|
||||
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ;
|
||||
content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
|
||||
|
||||
Services.ContentService.Save(content);
|
||||
return (content, () => content);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private IContentType GetOrCreateContentType()
|
||||
{
|
||||
var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias);
|
||||
if (docType != null)
|
||||
return docType;
|
||||
|
||||
docType = new ContentType(-1)
|
||||
{
|
||||
Alias = TestDataContentTypeAlias,
|
||||
Name = "Umbraco Test Data Content",
|
||||
Icon = "icon-science color-green"
|
||||
};
|
||||
docType.AddPropertyGroup("Content");
|
||||
docType.AddPropertyType(new PropertyType(GetOrCreateRichText(), "review")
|
||||
{
|
||||
Name = "Review"
|
||||
});
|
||||
docType.AddPropertyType(new PropertyType(GetOrCreateMediaPicker(), "media")
|
||||
{
|
||||
Name = "Media"
|
||||
});
|
||||
docType.AddPropertyType(new PropertyType(GetOrCreateText(), "desc")
|
||||
{
|
||||
Name = "Description"
|
||||
});
|
||||
Services.ContentTypeService.Save(docType);
|
||||
docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) };
|
||||
Services.ContentTypeService.Save(docType);
|
||||
return docType;
|
||||
}
|
||||
|
||||
private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce);
|
||||
|
||||
private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker);
|
||||
|
||||
private IDataType GetOrCreateText() => GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox);
|
||||
|
||||
private IDataType GetOrCreateDataType(string name, string editorAlias)
|
||||
{
|
||||
var dt = Services.DataTypeService.GetDataType(name);
|
||||
if (dt != null) return dt;
|
||||
|
||||
var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias);
|
||||
if (editor == null)
|
||||
throw new InvalidOperationException($"No {editorAlias} editor found");
|
||||
|
||||
dt = new DataType(editor)
|
||||
{
|
||||
Name = name,
|
||||
Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject,
|
||||
DatabaseType = ValueStorageType.Ntext
|
||||
};
|
||||
|
||||
Services.DataTypeService.Save(dt);
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
## Umbraco Test Data
|
||||
|
||||
This project is a utility to be able to generate large amounts of content and media in an
|
||||
Umbraco installation for testing.
|
||||
|
||||
Currently this project is referenced in the Umbraco.Web.UI project but only when it's being built
|
||||
in Debug mode (i.e. when testing within Visual Studio).
|
||||
|
||||
## Usage
|
||||
|
||||
You must use SQL Server for this, using SQLCE will die if you try to bulk create huge amounts of data.
|
||||
|
||||
It has to be enabled by an appSetting:
|
||||
|
||||
```xml
|
||||
<add key="Umbraco.TestData.Enabled" value="true"/>
|
||||
```
|
||||
|
||||
Once this is enabled this endpoint can be executed:
|
||||
|
||||
`/umbraco/surface/umbracotestdata/CreateTree?count=100&depth=5`
|
||||
|
||||
The query string options are:
|
||||
|
||||
* `count` = the number of content and media nodes to create
|
||||
* `depth` = how deep the trees created will be
|
||||
* `locale` (optional, default = "en") = the language that the data will be generated in
|
||||
|
||||
This creates a content and associated media tree (hierarchy). Each content item created is associated
|
||||
to a media item via a media picker and therefore a relation is created between the two. Each content and
|
||||
media tree created have the same root node name so it's easy to know which content branch relates to
|
||||
which media branch.
|
||||
|
||||
All values are generated using the very handy `Bogus` package.
|
||||
|
||||
## Schema
|
||||
|
||||
This will install some schema items:
|
||||
|
||||
* `umbTestDataContent` Document Type. __TIP__: If you want to delete all of the content data generated with this tool, just delete this content type
|
||||
* `UmbracoTestDataContent.RTE` Data Type
|
||||
* `UmbracoTestDataContent.MediaPicker` Data Type
|
||||
* `UmbracoTestDataContent.Text` Data Type
|
||||
|
||||
For media, the normal folder and image is used
|
||||
|
||||
## Media
|
||||
|
||||
This does not upload physical files, it just uses a randomized online image as the `umbracoFile` value.
|
||||
This works when viewing the media item in the media section and the image will show up and with recent changes this will also work
|
||||
when editing content to view the thumbnail for the picked media.
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -965,6 +971,32 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Verify_Content_Type_Has_Content_Nodes()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
ContentTypeRepository repository;
|
||||
var contentRepository = CreateRepository((IScopeAccessor)provider, out repository);
|
||||
var contentTypeId = NodeDto.NodeIdSeed + 1;
|
||||
var contentType = repository.Get(contentTypeId);
|
||||
|
||||
// Act
|
||||
var result = repository.HasContentNodes(contentTypeId);
|
||||
|
||||
var subpage = MockedContent.CreateTextpageContent(contentType, "Test Page 1", contentType.Id);
|
||||
contentRepository.Save(subpage);
|
||||
|
||||
var result2 = repository.HasContentNodes(contentTypeId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
Assert.That(result2, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateTestData()
|
||||
{
|
||||
//Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user