diff --git a/.gitignore b/.gitignore
index 022acb5db7..ea2ddfbb68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -191,6 +191,7 @@ src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb
/src/Umbraco.Web.UI.NetCore/App_Data/Smidge/Cache/*
/src/Umbraco.Web.UI.NetCore/umbraco/logs
+src/Umbraco.Tests.Integration/umbraco/Data/
src/Umbraco.Tests.Integration/umbraco/logs/
src/Umbraco.Tests.Integration/Views/
diff --git a/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs b/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs
deleted file mode 100644
index d88eb44ea3..0000000000
--- a/src/Umbraco.Core/Composing/IPublishedCacheComposer .cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Umbraco.Core.Composing
-{
- public interface IPublishedCacheComposer : ICoreComposer
- { }
-}
diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs
index 5d059d8a23..8199d9fbd0 100644
--- a/src/Umbraco.Core/Constants-Web.cs
+++ b/src/Umbraco.Core/Constants-Web.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Core
+namespace Umbraco.Core
{
public static partial class Constants
{
@@ -7,10 +7,6 @@
///
public static class Web
{
- public const string UmbracoContextDataToken = "umbraco-context";
- public const string UmbracoDataToken = "umbraco";
- public const string PublishedDocumentRequestDataToken = "umbraco-doc-request";
- public const string CustomRouteDataToken = "umbraco-custom-route";
public const string UmbracoRouteDefinitionDataToken = "umbraco-route-def";
///
diff --git a/src/Umbraco.Core/DisposableObjectSlim.cs b/src/Umbraco.Core/DisposableObjectSlim.cs
index 4992f8bc0f..6874ad8001 100644
--- a/src/Umbraco.Core/DisposableObjectSlim.cs
+++ b/src/Umbraco.Core/DisposableObjectSlim.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Umbraco.Core
{
@@ -6,8 +6,7 @@ namespace Umbraco.Core
/// Abstract implementation of managed IDisposable.
///
///
- /// This is for objects that do NOT have unmanaged resources. Use
- /// for objects that DO have unmanaged resources and need to deal with them when disposing.
+ /// This is for objects that do NOT have unmanaged resources.
///
/// Can also be used as a pattern for when inheriting is not possible.
///
@@ -19,35 +18,39 @@ namespace Umbraco.Core
///
public abstract class DisposableObjectSlim : IDisposable
{
- private readonly object _locko = new object();
-
- // gets a value indicating whether this instance is disposed.
- // for internal tests only (not thread safe)
+ ///
+ /// Gets a value indicating whether this instance is disposed.
+ ///
+ ///
+ /// for internal tests only (not thread safe)
+ ///
public bool Disposed { get; private set; }
- // implements IDisposable
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ ///
+ /// Disposes managed resources
+ ///
+ protected abstract void DisposeResources();
- private void Dispose(bool disposing)
+ ///
+ /// Disposes managed resources
+ ///
+ /// True if disposing via Dispose method and not a finalizer. Always true for this class.
+ protected virtual void Dispose(bool disposing)
{
- // can happen if the object construction failed
- if (_locko == null)
- return;
-
- lock (_locko)
+ if (!Disposed)
{
- if (Disposed) return;
+ if (disposing)
+ {
+ DisposeResources();
+ }
+
Disposed = true;
}
-
- if (disposing)
- DisposeResources();
}
- protected virtual void DisposeResources() { }
+ ///
+#pragma warning disable CA1063 // Implement IDisposable Correctly
+ public void Dispose() => Dispose(disposing: true); // We do not use GC.SuppressFinalize because this has no finalizer
+#pragma warning restore CA1063 // Implement IDisposable Correctly
}
}
diff --git a/src/Umbraco.Core/Events/IEventAggregator.cs b/src/Umbraco.Core/Events/IEventAggregator.cs
index bd01ad0b57..d78aed36b7 100644
--- a/src/Umbraco.Core/Events/IEventAggregator.cs
+++ b/src/Umbraco.Core/Events/IEventAggregator.cs
@@ -22,27 +22,4 @@ namespace Umbraco.Core.Events
Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default)
where TNotification : INotification;
}
-
- ///
- /// A marker interface to represent a notification.
- ///
- public interface INotification
- {
- }
-
- ///
- /// Defines a handler for a notification.
- ///
- /// The type of notification being handled.
- public interface INotificationHandler
- where TNotification : INotification
- {
- ///
- /// Handles a notification
- ///
- /// The notification
- /// The cancellation token.
- /// A representing the asynchronous operation.
- Task HandleAsync(TNotification notification, CancellationToken cancellationToken);
- }
}
diff --git a/src/Umbraco.Core/Events/INotification.cs b/src/Umbraco.Core/Events/INotification.cs
new file mode 100644
index 0000000000..3030b0836f
--- /dev/null
+++ b/src/Umbraco.Core/Events/INotification.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// A marker interface to represent a notification.
+ ///
+ public interface INotification
+ {
+ }
+}
diff --git a/src/Umbraco.Core/Events/INotificationHandler.cs b/src/Umbraco.Core/Events/INotificationHandler.cs
new file mode 100644
index 0000000000..dc5d83e0f7
--- /dev/null
+++ b/src/Umbraco.Core/Events/INotificationHandler.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core.Events
+{
+ ///
+ /// Defines a handler for a notification.
+ ///
+ /// The type of notification being handled.
+ public interface INotificationHandler
+ where TNotification : INotification
+ {
+ ///
+ /// Handles a notification
+ ///
+ /// The notification
+ /// The cancellation token.
+ /// A representing the asynchronous operation.
+ Task HandleAsync(TNotification notification, CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Umbraco.Core/Events/UmbracoApplicationStarting.cs b/src/Umbraco.Core/Events/UmbracoApplicationStarting.cs
new file mode 100644
index 0000000000..422673a823
--- /dev/null
+++ b/src/Umbraco.Core/Events/UmbracoApplicationStarting.cs
@@ -0,0 +1,16 @@
+namespace Umbraco.Core.Events
+{
+ public class UmbracoApplicationStarting : INotification
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The runtime level
+ public UmbracoApplicationStarting(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel;
+
+ ///
+ /// Gets the runtime level of execution.
+ ///
+ public RuntimeLevel RuntimeLevel { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Events/UmbracoApplicationStopping.cs b/src/Umbraco.Core/Events/UmbracoApplicationStopping.cs
new file mode 100644
index 0000000000..bef6f0de19
--- /dev/null
+++ b/src/Umbraco.Core/Events/UmbracoApplicationStopping.cs
@@ -0,0 +1,4 @@
+namespace Umbraco.Core.Events
+{
+ public class UmbracoApplicationStopping : INotification { }
+}
diff --git a/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs b/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
index a22748094a..28e1de3996 100644
--- a/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
+++ b/src/Umbraco.Core/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs
@@ -32,22 +32,16 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
///
/// Get the status for this health check
///
- ///
- public override IEnumerable GetStatus()
- {
- //return the statuses
- return new[] { CheckFolderPermissions(), CheckFilePermissions() };
- }
+ // TODO: This should really just run the IFilePermissionHelper.RunFilePermissionTestSuite and then we'd have a
+ // IFilePermissions interface resolved as a collection within the IFilePermissionHelper that runs checks against all
+ // IFilePermissions registered. Then there's no hard coding things done here and the checks here will be consistent
+ // with the checks run in IFilePermissionHelper.RunFilePermissionTestSuite which occurs on install too.
+ public override IEnumerable GetStatus() => new[] { CheckFolderPermissions(), CheckFilePermissions() };
///
/// Executes the action and returns it's status
///
- ///
- ///
- public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
- {
- throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions");
- }
+ public override HealthCheckStatus ExecuteAction(HealthCheckAction action) => throw new InvalidOperationException("FolderAndFilePermissionsCheck has no executable actions");
private HealthCheckStatus CheckFolderPermissions()
{
@@ -67,8 +61,8 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
{ Constants.SystemDirectories.MvcViews, PermissionCheckRequirement.Optional }
};
- //These are special paths to check that will restart an app domain if a file is written to them,
- //so these need to be tested differently
+ // These are special paths to check that will restart an app domain if a file is written to them,
+ // so these need to be tested differently
var pathsToCheckWithRestarts = new Dictionary
{
{ Constants.SystemDirectories.Bin, PermissionCheckRequirement.Optional }
@@ -80,7 +74,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
var optionalPathCheckResult = _filePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out var optionalFailedPaths);
- //now check the special folders
+ // now check the special folders
var requiredPathCheckResult2 = _filePermissionHelper.EnsureDirectories(
GetPathsToCheck(pathsToCheckWithRestarts, PermissionCheckRequirement.Required), out var requiredFailedPaths2, writeCausesRestart: true);
var optionalPathCheckResult2 = _filePermissionHelper.EnsureDirectories(
@@ -89,7 +83,7 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
requiredPathCheckResult = requiredPathCheckResult && requiredPathCheckResult2;
optionalPathCheckResult = optionalPathCheckResult && optionalPathCheckResult2;
- //combine the paths
+ // combine the paths
requiredFailedPaths = requiredFailedPaths.Concat(requiredFailedPaths2).ToList();
optionalFailedPaths = requiredFailedPaths.Concat(optionalFailedPaths2).ToList();
@@ -106,23 +100,19 @@ namespace Umbraco.Core.HealthCheck.Checks.Permissions
};
// Run checks for required and optional paths for modify permission
- IEnumerable requiredFailedPaths;
- IEnumerable optionalFailedPaths;
- var requiredPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out requiredFailedPaths);
- var optionalPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out optionalFailedPaths);
+ var requiredPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Required), out IEnumerable requiredFailedPaths);
+ var optionalPathCheckResult = _filePermissionHelper.EnsureFiles(GetPathsToCheck(pathsToCheck, PermissionCheckRequirement.Optional), out IEnumerable optionalFailedPaths);
return GetStatus(requiredPathCheckResult, requiredFailedPaths, optionalPathCheckResult, optionalFailedPaths, PermissionCheckFor.File);
}
- private string[] GetPathsToCheck(Dictionary pathsToCheck,
- PermissionCheckRequirement requirement)
- {
- return pathsToCheck
+ private string[] GetPathsToCheck(
+ Dictionary pathsToCheck,
+ PermissionCheckRequirement requirement) => pathsToCheck
.Where(x => x.Value == requirement)
.Select(x => _hostingEnvironment.MapPathContentRoot(x.Key))
.OrderBy(x => x)
.ToArray();
- }
private HealthCheckStatus GetStatus(bool requiredPathCheckResult, IEnumerable requiredFailedPaths, bool optionalPathCheckResult, IEnumerable optionalFailedPaths, PermissionCheckFor checkingFor)
{
diff --git a/src/Umbraco.Core/Install/IFilePermissionHelper.cs b/src/Umbraco.Core/Install/IFilePermissionHelper.cs
index b60839cb00..ab521d214e 100644
--- a/src/Umbraco.Core/Install/IFilePermissionHelper.cs
+++ b/src/Umbraco.Core/Install/IFilePermissionHelper.cs
@@ -1,11 +1,26 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace Umbraco.Core.Install
{
public interface IFilePermissionHelper
{
bool RunFilePermissionTestSuite(out Dictionary> report);
+
+ ///
+ /// This will test the directories for write access
+ ///
+ /// The directories to check
+ /// The resulting errors, if any
+ ///
+ /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause
+ /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as
+ /// reliable but we cannot write a file since it will cause an app domain restart.
+ ///
+ /// Returns true if test succeeds
+ // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus
bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false);
+
+ // TODO: This shouldn't exist, see notes in FolderAndFilePermissionsCheck.GetStatus
bool EnsureFiles(string[] files, out IEnumerable errors);
}
}
diff --git a/src/Umbraco.Core/Manifest/ManifestWatcher.cs b/src/Umbraco.Core/Manifest/ManifestWatcher.cs
index 6bd893d298..e74393a179 100644
--- a/src/Umbraco.Core/Manifest/ManifestWatcher.cs
+++ b/src/Umbraco.Core/Manifest/ManifestWatcher.cs
@@ -1,14 +1,13 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
-using Umbraco.Core.Hosting;
using Umbraco.Net;
namespace Umbraco.Core.Manifest
{
- public class ManifestWatcher : DisposableObjectSlim
+ public class ManifestWatcher : IDisposable
{
private static readonly object Locker = new object();
private static volatile bool _isRestarting;
@@ -16,6 +15,7 @@ namespace Umbraco.Core.Manifest
private readonly ILogger _logger;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
private readonly List _fws = new List();
+ private bool _disposed;
public ManifestWatcher(ILogger logger, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
@@ -48,7 +48,10 @@ namespace Umbraco.Core.Manifest
private void FswChanged(object sender, FileSystemEventArgs e)
{
- if (e.Name.InvariantContains("package.manifest") == false) return;
+ if (!e.Name.InvariantContains("package.manifest"))
+ {
+ return;
+ }
// ensure the app is not restarted multiple times for multiple
// savings during the same app domain execution - restart once
@@ -59,14 +62,25 @@ namespace Umbraco.Core.Manifest
_isRestarting = true;
_logger.LogInformation("Manifest has changed, app pool is restarting ({Path})", e.FullPath);
_umbracoApplicationLifetime.Restart();
- Dispose(); // uh? if the app restarts then this should be disposed anyways?
}
}
- protected override void DisposeResources()
+ private void Dispose(bool disposing)
{
- foreach (var fw in _fws)
- fw.Dispose();
+ // ReSharper disable InvertIf
+ if (disposing && !_disposed)
+ {
+ foreach (FileSystemWatcher fw in _fws)
+ {
+ fw.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ // ReSharper restore InvertIf
}
+
+ public void Dispose() => Dispose(true);
}
}
diff --git a/src/Umbraco.Core/Models/IContentModel.cs b/src/Umbraco.Core/Models/IContentModel.cs
index d0d4f175d7..692547aa3e 100644
--- a/src/Umbraco.Core/Models/IContentModel.cs
+++ b/src/Umbraco.Core/Models/IContentModel.cs
@@ -1,10 +1,30 @@
-using Umbraco.Core.Models;
+using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Models
{
+ ///
+ /// The basic view model returned for front-end Umbraco controllers
+ ///
+ ///
+ ///
+ /// exists in order to unify all view models in Umbraco, whether it's a normal template view or a partial view macro, or
+ /// a user's custom model that they have created when doing route hijacking or custom routes.
+ ///
+ ///
+ /// By default all front-end template views inherit from UmbracoViewPage which has a model of but the model returned
+ /// from the controllers is which in normal circumstances would not work. This works with UmbracoViewPage because it
+ /// performs model binding between IContentModel and IPublishedContent. This offers a lot of flexibility when rendering views. In some cases if you
+ /// are route hijacking and returning a custom implementation of and your view is strongly typed to this model, you can still
+ /// render partial views created in the back office that have the default model of IPublishedContent without having to worry about explicitly passing
+ /// that model to the view.
+ ///
+ ///
public interface IContentModel
{
+ ///
+ /// Gets the
+ ///
IPublishedContent Content { get; }
}
}
diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs
index cfc789324a..f9330176aa 100644
--- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContentType.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
namespace Umbraco.Core.Models.PublishedContent
@@ -8,21 +8,13 @@ namespace Umbraco.Core.Models.PublishedContent
///
/// Instances implementing the interface should be
/// immutable, ie if the content type changes, then a new instance needs to be created.
- public interface IPublishedContentType2 : IPublishedContentType
+ public interface IPublishedContentType
{
///
/// Gets the unique key for the content type.
///
Guid Key { get; }
- }
- ///
- /// Represents an type.
- ///
- /// Instances implementing the interface should be
- /// immutable, ie if the content type changes, then a new instance needs to be created.
- public interface IPublishedContentType
- {
///
/// Gets the content type identifier.
///
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
index 14c26442eb..daf75f5c50 100644
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
+++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.PublishedContent
///
/// Instances of the class are immutable, ie
/// if the content type changes, then a new class needs to be created.
- public class PublishedContentType : IPublishedContentType2
+ public class PublishedContentType : IPublishedContentType
{
private readonly IPublishedPropertyType[] _propertyTypes;
diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs
deleted file mode 100644
index feab33c1d6..0000000000
--- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentTypeExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Models.PublishedContent
-{
- public static class PublishedContentTypeExtensions
- {
- ///
- /// Get the GUID key from an
- ///
- ///
- ///
- ///
- public static bool TryGetKey(this IPublishedContentType publishedContentType, out Guid key)
- {
- if (publishedContentType is IPublishedContentType2 contentTypeWithKey)
- {
- key = contentTypeWithKey.Key;
- return true;
- }
- key = Guid.Empty;
- return false;
- }
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
index 68b2367ce0..7307ba97f9 100644
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
+++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
-using Umbraco.Core.Models.Membership;
+using System.Threading.Tasks;
using Umbraco.Web.Cache;
namespace Umbraco.Web.PublishedCache
@@ -11,8 +11,6 @@ namespace Umbraco.Web.PublishedCache
///
public interface IPublishedSnapshotService : IDisposable
{
- #region PublishedSnapshot
-
/* Various places (such as Node) want to access the XML content, today as an XmlDocument
* but to migrate to a new cache, they're migrating to an XPathNavigator. Still, they need
* to find out how to get that navigator.
@@ -36,85 +34,23 @@ namespace Umbraco.Web.PublishedCache
IPublishedSnapshot CreatePublishedSnapshot(string previewToken);
///
- /// Gets the published snapshot accessor.
- ///
- IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
-
- ///
- /// Ensures that the published snapshot has the proper environment to run.
- ///
- /// The errors, if any.
- /// A value indicating whether the published snapshot has the proper environment to run.
- bool EnsureEnvironment(out IEnumerable errors);
-
- #endregion
-
- #region Rebuild
-
- ///
- /// Rebuilds internal caches (but does not reload).
+ /// Rebuilds internal database caches (but does not reload).
///
+ /// The operation batch size to process the items
+ /// If not null will process content for the matching content types, if empty will process all content
+ /// If not null will process content for the matching media types, if empty will process all media
+ /// If not null will process content for the matching members types, if empty will process all members
///
- /// Forces the snapshot service to rebuild its internal caches. For instance, some caches
+ /// Forces the snapshot service to rebuild its internal database caches. For instance, some caches
/// may rely on a database table to store pre-serialized version of documents.
/// This does *not* reload the caches. Caches need to be reloaded, for instance via
/// RefreshAllPublishedSnapshot method.
///
- void Rebuild();
-
- #endregion
-
- #region Preview
-
- /* Later on we can imagine that EnterPreview would handle a "level" that would be either
- * the content only, or the content's branch, or the whole tree + it could be possible
- * to register filters against the factory to filter out which nodes should be preview
- * vs non preview.
- *
- * EnterPreview() returns the previewToken. It is up to callers to store that token
- * wherever they want, most probably in a cookie.
- *
- */
-
- ///
- /// Enters preview for specified user and content.
- ///
- /// The user.
- /// The content identifier.
- /// A preview token.
- ///
- /// Tells the caches that they should prepare any data that they would be keeping
- /// in order to provide preview to a give user. In the Xml cache this means creating the Xml
- /// file, though other caches may do things differently.
- /// Does not handle the preview token storage (cookie, etc) that must be handled separately.
- ///
- string EnterPreview(IUser user, int contentId);
-
- ///
- /// Refreshes preview for a specified content.
- ///
- /// The preview token.
- /// The content identifier.
- /// Tells the caches that they should update any data that they would be keeping
- /// in order to provide preview to a given user. In the Xml cache this means updating the Xml
- /// file, though other caches may do things differently.
- void RefreshPreview(string previewToken, int contentId);
-
- ///
- /// Exits preview for a specified preview token.
- ///
- /// The preview token.
- ///
- /// Tells the caches that they can dispose of any data that they would be keeping
- /// in order to provide preview to a given user. In the Xml cache this means deleting the Xml file,
- /// though other caches may do things differently.
- /// Does not handle the preview token storage (cookie, etc) that must be handled separately.
- ///
- void ExitPreview(string previewToken);
-
- #endregion
-
- #region Changes
+ void Rebuild(
+ int groupSize = 5000,
+ IReadOnlyCollection contentTypeIds = null,
+ IReadOnlyCollection mediaTypeIds = null,
+ IReadOnlyCollection memberTypeIds = null);
/* An IPublishedCachesService implementation can rely on transaction-level events to update
* its internal, database-level data, as these events are purely internal. However, it cannot
@@ -160,16 +96,9 @@ namespace Umbraco.Web.PublishedCache
/// The changes.
void Notify(DomainCacheRefresher.JsonPayload[] payloads);
- #endregion
-
- #region Status
-
- string GetStatus();
-
- string StatusUrl { get; }
-
- #endregion
-
- void Collect();
+ ///
+ /// Cleans up unused snapshots
+ ///
+ Task CollectAsync();
}
}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs
new file mode 100644
index 0000000000..0f88bd4085
--- /dev/null
+++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotStatus.cs
@@ -0,0 +1,18 @@
+namespace Umbraco.Web.PublishedCache
+{
+ ///
+ /// Returns the currents status for nucache
+ ///
+ public interface IPublishedSnapshotStatus
+ {
+ ///
+ /// Gets the status report as a string
+ ///
+ string GetStatus();
+
+ ///
+ /// Gets the URL used to retreive the status
+ ///
+ string StatusUrl { get; }
+ }
+}
diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
deleted file mode 100644
index 9c71bdc04b..0000000000
--- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Models.PublishedContent;
-using Umbraco.Web.Cache;
-
-namespace Umbraco.Web.PublishedCache
-{
- public abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService
- {
- protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor)
- {
- PublishedSnapshotAccessor = publishedSnapshotAccessor;
- VariationContextAccessor = variationContextAccessor;
- }
-
- public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
- public IVariationContextAccessor VariationContextAccessor { get; }
-
- // note: NOT setting _publishedSnapshotAccessor.PublishedSnapshot here because it is the
- // responsibility of the caller to manage what the 'current' facade is
- public abstract IPublishedSnapshot CreatePublishedSnapshot(string previewToken);
-
- protected IPublishedSnapshot CurrentPublishedSnapshot => PublishedSnapshotAccessor.PublishedSnapshot;
-
- public abstract bool EnsureEnvironment(out IEnumerable errors);
-
- public abstract string EnterPreview(IUser user, int contentId);
- public abstract void RefreshPreview(string previewToken, int contentId);
- public abstract void ExitPreview(string previewToken);
- public abstract void Notify(ContentCacheRefresher.JsonPayload[] payloads, out bool draftChanged, out bool publishedChanged);
- public abstract void Notify(MediaCacheRefresher.JsonPayload[] payloads, out bool anythingChanged);
- public abstract void Notify(ContentTypeCacheRefresher.JsonPayload[] payloads);
- public abstract void Notify(DataTypeCacheRefresher.JsonPayload[] payloads);
- public abstract void Notify(DomainCacheRefresher.JsonPayload[] payloads);
-
- public virtual void Rebuild()
- { }
-
- public virtual void Dispose()
- { }
-
- public abstract string GetStatus();
-
- public virtual string StatusUrl => "views/dashboard/settings/publishedsnapshotcache.html";
-
- public virtual void Collect()
- {
- }
- }
-}
diff --git a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
index 7e0ebc6f13..874da1f3aa 100644
--- a/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
+++ b/src/Umbraco.Core/PublishedCache/UmbracoContextPublishedSnapshotAccessor.cs
@@ -1,6 +1,11 @@
-using System;
+using System;
namespace Umbraco.Web.PublishedCache
{
+ // TODO: This is a mess. This is a circular reference:
+ // IPublishedSnapshotAccessor -> PublishedSnapshotService -> UmbracoContext -> PublishedSnapshotService -> IPublishedSnapshotAccessor
+ // Injecting IPublishedSnapshotAccessor into PublishedSnapshotService seems pretty strange
+ // The underlying reason for this mess is because IPublishedContent is both a service and a model.
+ // Until that is fixed, IPublishedContent will need to have a IPublishedSnapshotAccessor
public class UmbracoContextPublishedSnapshotAccessor : IPublishedSnapshotAccessor
{
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
diff --git a/src/Umbraco.Core/Routing/IPublishedRequest.cs b/src/Umbraco.Core/Routing/IPublishedRequest.cs
index f357108a4e..51fc9ccf64 100644
--- a/src/Umbraco.Core/Routing/IPublishedRequest.cs
+++ b/src/Umbraco.Core/Routing/IPublishedRequest.cs
@@ -11,7 +11,7 @@ namespace Umbraco.Web.Routing
///
/// Gets the UmbracoContext.
///
- IUmbracoContext UmbracoContext { get; }
+ IUmbracoContext UmbracoContext { get; } // TODO: This should be injected and removed from here
///
/// Gets or sets the cleaned up Uri used for routing.
diff --git a/src/Umbraco.Core/Routing/PublishedRouter.cs b/src/Umbraco.Core/Routing/PublishedRouter.cs
index 9b7354de74..10986b941a 100644
--- a/src/Umbraco.Core/Routing/PublishedRouter.cs
+++ b/src/Umbraco.Core/Routing/PublishedRouter.cs
@@ -138,7 +138,7 @@ namespace Umbraco.Web.Routing
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
SetVariationContext(request.Culture.Name);
- //find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
+ // find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
if (request.PublishedContent == null)
@@ -156,15 +156,13 @@ namespace Umbraco.Web.Routing
// trigger the Prepared event - at that point it is still possible to change about anything
// even though the request might be flagged for redirection - we'll redirect _after_ the event
- //
// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change
- //
request.OnPrepared();
// we don't take care of anything so if the content has changed, it's up to the user
// to find out the appropriate template
- //complete the PCR and assign the remaining values
+ // complete the PCR and assign the remaining values
return ConfigureRequest(request);
}
@@ -201,7 +199,6 @@ namespace Umbraco.Web.Routing
// can't go beyond that point without a PublishedContent to render
// it's ok not to have a template, in order to give MVC a chance to hijack routes
-
return true;
}
diff --git a/src/Umbraco.Core/Services/IRuntime.cs b/src/Umbraco.Core/Services/IRuntime.cs
index 8a1be721d0..d1254c219f 100644
--- a/src/Umbraco.Core/Services/IRuntime.cs
+++ b/src/Umbraco.Core/Services/IRuntime.cs
@@ -1,22 +1,15 @@
-using System;
+using Microsoft.Extensions.Hosting;
namespace Umbraco.Core
{
///
/// Defines the Umbraco runtime.
///
- public interface IRuntime
+ public interface IRuntime : IHostedService
{
///
/// Gets the runtime state.
///
IRuntimeState State { get; }
-
- void Start();
-
- ///
- /// Terminates the runtime.
- ///
- void Terminate();
}
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 300dedc1c6..24b2fae683 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs b/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs
index 74e2dd7380..a8521762c6 100644
--- a/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs
+++ b/src/Umbraco.Core/UmbracoContextAccessorExtensions.cs
@@ -3,6 +3,7 @@ using Umbraco.Web;
namespace Umbraco.Core
{
+
public static class UmbracoContextAccessorExtensions
{
public static IUmbracoContext GetRequiredUmbracoContext(this IUmbracoContextAccessor umbracoContextAccessor)
diff --git a/src/Umbraco.Core/UmbracoContextExtensions.cs b/src/Umbraco.Core/UmbracoContextExtensions.cs
new file mode 100644
index 0000000000..06ae2aa497
--- /dev/null
+++ b/src/Umbraco.Core/UmbracoContextExtensions.cs
@@ -0,0 +1,12 @@
+using Umbraco.Web;
+
+namespace Umbraco.Core
+{
+ public static class UmbracoContextExtensions
+ {
+ ///
+ /// Boolean value indicating whether the current request is a front-end umbraco request
+ ///
+ public static bool IsFrontEndUmbracoRequest(this IUmbracoContext umbracoContext) => umbracoContext.PublishedRequest != null;
+ }
+}
diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs
index 0452373d55..ea846f7f7a 100644
--- a/src/Umbraco.Core/UriExtensions.cs
+++ b/src/Umbraco.Core/UriExtensions.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
@@ -100,16 +100,13 @@ namespace Umbraco.Core
return false;
}
- //if its anything else we can assume it's back office
+ // if its anything else we can assume it's back office
return true;
}
///
/// Checks if the current uri is an install request
///
- ///
- ///
- ///
public static bool IsInstallerRequest(this Uri url, IHostingEnvironment hostingEnvironment)
{
var authority = url.GetLeftPart(UriPartial.Authority);
@@ -117,18 +114,14 @@ namespace Umbraco.Core
.TrimStart(authority)
.TrimStart("/");
- //check if this is in the umbraco back office
+ // check if this is in the umbraco back office
return afterAuthority.InvariantStartsWith(hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install).TrimStart("/"));
}
///
/// Checks if the uri is a request for the default back office page
///
- ///
- ///
- ///
- ///
- internal static bool IsDefaultBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
+ public static bool IsDefaultBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment)
{
var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment);
if (url.AbsolutePath.InvariantEquals(backOfficePath.TrimEnd("/"))
@@ -138,6 +131,7 @@ namespace Umbraco.Core
{
return true;
}
+
return false;
}
diff --git a/src/Umbraco.Core/HybridUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs
similarity index 100%
rename from src/Umbraco.Core/HybridUmbracoContextAccessor.cs
rename to src/Umbraco.Core/Web/HybridUmbracoContextAccessor.cs
diff --git a/src/Umbraco.Core/IUmbracoContext.cs b/src/Umbraco.Core/Web/IUmbracoContext.cs
similarity index 90%
rename from src/Umbraco.Core/IUmbracoContext.cs
rename to src/Umbraco.Core/Web/IUmbracoContext.cs
index 681dedbfd2..c034997d7e 100644
--- a/src/Umbraco.Core/IUmbracoContext.cs
+++ b/src/Umbraco.Core/Web/IUmbracoContext.cs
@@ -6,7 +6,7 @@ using Umbraco.Web.Routing;
namespace Umbraco.Web
{
- public interface IUmbracoContext
+ public interface IUmbracoContext : IDisposable
{
///
/// This is used internally for performance calculations, the ObjectCreated DateTime is set as soon as this
@@ -46,14 +46,10 @@ namespace Umbraco.Web
///
IDomainCache Domains { get; }
- ///
- /// Boolean value indicating whether the current request is a front-end umbraco request
- ///
- bool IsFrontEndUmbracoRequest { get; }
-
///
/// Gets/sets the PublishedRequest object
///
+ // TODO: Can we refactor this and not expose this mutable object here? Instead just expose IPublishedContent? A bunch of stuff would need to change but would be better
IPublishedRequest PublishedRequest { get; set; }
///
@@ -73,6 +69,5 @@ namespace Umbraco.Web
bool InPreviewMode { get; }
IDisposable ForcedPreview(bool preview);
- void Dispose();
}
}
diff --git a/src/Umbraco.Core/IUmbracoContextAccessor.cs b/src/Umbraco.Core/Web/IUmbracoContextAccessor.cs
similarity index 100%
rename from src/Umbraco.Core/IUmbracoContextAccessor.cs
rename to src/Umbraco.Core/Web/IUmbracoContextAccessor.cs
diff --git a/src/Umbraco.Core/IUmbracoContextFactory.cs b/src/Umbraco.Core/Web/IUmbracoContextFactory.cs
similarity index 100%
rename from src/Umbraco.Core/IUmbracoContextFactory.cs
rename to src/Umbraco.Core/Web/IUmbracoContextFactory.cs
diff --git a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs b/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs
deleted file mode 100644
index dd3a800821..0000000000
--- a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComponent.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System.IO;
-using Microsoft.Extensions.Logging;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Hosting;
-using Umbraco.Core.Manifest;
-using Umbraco.Net;
-
-namespace Umbraco.Core.Compose
-{
- public sealed class ManifestWatcherComponent : IComponent
- {
- private readonly IHostingEnvironment _hosting;
- private readonly ILoggerFactory _loggerFactory;
- private readonly IHostingEnvironment _hostingEnvironment;
- private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
-
- // if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for
- // package.manifest chances and restarts the application on any change
- private ManifestWatcher _mw;
-
- public ManifestWatcherComponent(IHostingEnvironment hosting, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime)
- {
- _hosting = hosting;
- _loggerFactory = loggerFactory;
- _hostingEnvironment = hostingEnvironment;
- _umbracoApplicationLifetime = umbracoApplicationLifetime;
- }
-
- public void Initialize()
- {
- if (_hosting.IsDebugMode == false) return;
-
- //if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false)
- // return;
-
- var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
- if (Directory.Exists(appPlugins) == false) return;
-
- _mw = new ManifestWatcher(_loggerFactory.CreateLogger(), _umbracoApplicationLifetime);
- _mw.Start(Directory.GetDirectories(appPlugins));
- }
-
- public void Terminate()
- {
- if (_mw == null) return;
-
- _mw.Dispose();
- _mw = null;
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComposer.cs b/src/Umbraco.Infrastructure/Compose/ManifestWatcherComposer.cs
deleted file mode 100644
index 786a3ed0ce..0000000000
--- a/src/Umbraco.Infrastructure/Compose/ManifestWatcherComposer.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Umbraco.Core.Composing;
-
-namespace Umbraco.Core.Compose
-{
- public class ManifestWatcherComposer : ComponentComposer, ICoreComposer
- { }
-}
diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
deleted file mode 100644
index 7c17a8a338..0000000000
--- a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Microsoft.Extensions.Hosting;
-
-namespace Umbraco.Core.Composing
-{
- ///
- /// Extends the to enable Umbraco to be used as the service container.
- ///
- public static class HostBuilderExtensions
- {
- ///
- /// Assigns a custom service provider factory to use Umbraco's container
- ///
- ///
- ///
- public static IHostBuilder UseUmbraco(this IHostBuilder builder)
- {
- return builder;
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs b/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs
index 5642c882e6..ca25563730 100644
--- a/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs
+++ b/src/Umbraco.Infrastructure/Configuration/NCronTabParser.cs
@@ -1,9 +1,8 @@
-using System;
+using System;
using NCrontab;
namespace Umbraco.Core.Configuration
{
-
public class NCronTabParser : ICronTabParser
{
public bool IsValidCronTab(string cronTab)
diff --git a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs
index a12634f01d..00f7c80fe4 100644
--- a/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs
+++ b/src/Umbraco.Infrastructure/Install/FilePermissionHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
@@ -20,7 +20,7 @@ namespace Umbraco.Web.Install
private readonly string[] _packagesPermissionsDirs;
// ensure Umbraco can write to these files (the directories must exist)
- private readonly string[] _permissionFiles = { };
+ private readonly string[] _permissionFiles = Array.Empty();
private readonly GlobalSettings _globalSettings;
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
@@ -49,26 +49,13 @@ namespace Umbraco.Web.Install
if (EnsureFiles(_permissionFiles, out errors) == false)
report["File writing failed"] = errors.ToList();
- if (TestPublishedSnapshotService(out errors) == false)
- report["Published snapshot environment check failed"] = errors.ToList();
-
if (EnsureCanCreateSubDirectory(_globalSettings.UmbracoMediaPath, out errors) == false)
report["Media folder creation failed"] = errors.ToList();
return report.Count == 0;
}
- ///
- /// This will test the directories for write access
- ///
- ///
- ///
- ///
- /// If this is false, the easiest way to test for write access is to write a temp file, however some folder will cause
- /// an App Domain restart if a file is written to the folder, so in that case we need to use the ACL APIs which aren't as
- /// reliable but we cannot write a file since it will cause an app domain restart.
- ///
- ///
+ ///
public bool EnsureDirectories(string[] dirs, out IEnumerable errors, bool writeCausesRestart = false)
{
List temp = null;
@@ -96,9 +83,16 @@ namespace Umbraco.Web.Install
foreach (var file in files)
{
var canWrite = TryWriteFile(file);
- if (canWrite) continue;
+ if (canWrite)
+ {
+ continue;
+ }
+
+ if (temp == null)
+ {
+ temp = new List();
+ }
- if (temp == null) temp = new List();
temp.Add(file);
success = false;
}
@@ -130,11 +124,6 @@ namespace Umbraco.Web.Install
return success;
}
- public bool TestPublishedSnapshotService(out IEnumerable errors)
- {
- return _publishedSnapshotService.EnsureEnvironment(out errors);
- }
-
// tries to create a sub-directory
// if successful, the sub-directory is deleted
// creates the directory if needed - does not delete it
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs
index a611186021..2aa450b7b9 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs
@@ -1,4 +1,4 @@
-using System.Data;
+using System.Data;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
index 4031047970..59387fcb9f 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents the NPoco implementation of .
///
- internal class AuditEntryRepository : NPocoRepositoryBase, IAuditEntryRepository
+ internal class AuditEntryRepository : EntityRepositoryBase, IAuditEntryRepository
{
///
/// Initializes a new instance of the class.
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
index a42019e59f..8ad370672e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using NPoco;
@@ -12,7 +12,7 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class AuditRepository : NPocoRepositoryBase, IAuditRepository
+ internal class AuditRepository : EntityRepositoryBase, IAuditRepository
{
public AuditRepository(IScopeAccessor scopeAccessor, ILogger logger)
: base(scopeAccessor, AppCaches.NoCache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs
index 47ebddf698..cff06a2126 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using NPoco;
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents the NPoco implementation of .
///
- internal class ConsentRepository : NPocoRepositoryBase, IConsentRepository
+ internal class ConsentRepository : EntityRepositoryBase, IConsentRepository
{
///
/// Initializes a new instance of the class.
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 2533eaea8e..7b90efd4ae 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public static bool ThrowOnWarning = false;
}
- public abstract class ContentRepositoryBase : NPocoRepositoryBase, IContentRepository
+ public abstract class ContentRepositoryBase : EntityRepositoryBase, IContentRepository
where TEntity : class, IContentBase
where TRepository : class, IRepository
{
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected ContentRepositoryBase(
IScopeAccessor scopeAccessor,
AppCaches cache,
- ILogger> logger,
+ ILogger> logger,
ILanguageRepository languageRepository,
IRelationRepository relationRepository,
IRelationTypeRepository relationTypeRepository,
@@ -767,8 +767,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region UnitOfWork Events
- // TODO: The reason these events are in the repository is for legacy, the events should exist at the service
- // level now since we can fire these events within the transaction... so move the events to service level
+ /*
+ * TODO: The reason these events are in the repository is for legacy, the events should exist at the service
+ * level now since we can fire these events within the transaction...
+ * The reason these events 'need' to fire in the transaction is to ensure data consistency with Nucache (currently
+ * the only thing that uses them). For example, if the transaction succeeds and NuCache listened to ContentService.Saved
+ * and then NuCache failed at persisting data after the trans completed, then NuCache would be out of sync. This way
+ * the entire trans is rolled back if NuCache files. This is part of the discussion about removing the static events,
+ * possibly there's 3 levels of eventing, "ing", "scoped" (in trans) and "ed" (after trans).
+ * These particular events can be moved to the service level. However, see the notes below, it seems the only event we
+ * really need is the ScopedEntityRefresh. The only tricky part with moving that to the service level is that the
+ * handlers of that event will need to deal with the data a little differently because it seems that the
+ * "Published" flag on the content item matters and this event is raised before that flag is switched. Weird.
+ * We have the ability with IContent to see if something "WasPublished", etc.. so i think we could still use that.
+ */
public class ScopedEntityEventArgs : EventArgs
{
@@ -784,6 +796,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public class ScopedVersionEventArgs : EventArgs
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
public ScopedVersionEventArgs(IScope scope, int entityId, int versionId)
{
Scope = scope;
@@ -791,13 +806,43 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
VersionId = versionId;
}
+ ///
+ /// Gets the current
+ ///
public IScope Scope { get; }
+
+ ///
+ /// Gets the entity id
+ ///
public int EntityId { get; }
+
+ ///
+ /// Gets the version id
+ ///
public int VersionId { get; }
}
+ ///
+ /// Occurs when an is created or updated from within the (transaction)
+ ///
public static event TypedEventHandler ScopedEntityRefresh;
+
+ ///
+ /// Occurs when an is being deleted from within the (transaction)
+ ///
+ ///
+ /// TODO: This doesn't seem to be necessary at all, the service "Deleting" events for this would work just fine
+ /// since they are raised before the item is actually deleted just like this event.
+ ///
public static event TypedEventHandler ScopeEntityRemove;
+
+ ///
+ /// Occurs when a version for an is being deleted from within the (transaction)
+ ///
+ ///
+ /// TODO: This doesn't seem to be necessary at all, the service "DeletingVersions" events for this would work just fine
+ /// since they are raised before the item is actually deleted just like this event.
+ ///
public static event TypedEventHandler ScopeVersionRemove;
// used by tests to clear events
@@ -808,20 +853,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
ScopeVersionRemove = null;
}
+ ///
+ /// Raises the event
+ ///
protected void OnUowRefreshedEntity(ScopedEntityEventArgs args)
- {
- ScopedEntityRefresh.RaiseEvent(args, This);
- }
+ => ScopedEntityRefresh.RaiseEvent(args, This);
+ ///
+ /// Raises the event
+ ///
protected void OnUowRemovingEntity(ScopedEntityEventArgs args)
- {
- ScopeEntityRemove.RaiseEvent(args, This);
- }
+ => ScopeEntityRemove.RaiseEvent(args, This);
+ ///
+ /// Raises the event
+ ///
protected void OnUowRemovingVersion(ScopedVersionEventArgs args)
- {
- ScopeVersionRemove.RaiseEvent(args, This);
- }
+ => ScopeVersionRemove.RaiseEvent(args, This);
#endregion
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index 26596410bf..6554782d24 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Exposes shared functionality
///
- internal abstract class ContentTypeRepositoryBase : NPocoRepositoryBase, IReadRepository
+ internal abstract class ContentTypeRepositoryBase : EntityRepositoryBase, IReadRepository
where TEntity : class, IContentTypeComposition
{
private readonly IShortStringHelper _shortStringHelper;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs
index 482a333631..1f614e7647 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents a repository for doing CRUD operations for
///
- internal class DataTypeRepository : NPocoRepositoryBase, IDataTypeRepository
+ internal class DataTypeRepository : EntityRepositoryBase, IDataTypeRepository
{
private readonly Lazy _editors;
private readonly IConfigurationEditorJsonSerializer _serializer;
@@ -81,7 +81,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
index 0c58d26a2a..abab07a7bb 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents a repository for doing CRUD operations for
///
- internal class DictionaryRepository : NPocoRepositoryBase, IDictionaryRepository
+ internal class DictionaryRepository : EntityRepositoryBase, IDictionaryRepository
{
private readonly ILoggerFactory _loggerFactory;
@@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 6ed884eb0c..f3b9ca58d6 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -952,7 +952,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// reading repository purely for looking up by GUID
// TODO: ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things!
- private class ContentByGuidReadRepository : NPocoRepositoryBase
+ private class ContentByGuidReadRepository : EntityRepositoryBase
{
private readonly DocumentRepository _outerRepo;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs
index f0315f747c..e9e62d76c9 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// TODO: We need to get a readonly ISO code for the domain assigned
- internal class DomainRepository : NPocoRepositoryBase, IDomainRepository
+ internal class DomainRepository : EntityRepositoryBase, IDomainRepository
{
public DomainRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
index 36213b089f..26159c4fdf 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// An internal repository for managing entity containers such as doc type, media type, data type containers.
///
- internal class EntityContainerRepository : NPocoRepositoryBase, IEntityContainerRepository
+ internal class EntityContainerRepository : EntityRepositoryBase, IEntityContainerRepository
{
private readonly Guid _containerObjectType;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
index 61ced57149..41f6a065d4 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepository.cs
@@ -1,15 +1,16 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using NPoco;
+using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Scoping;
-using static Umbraco.Core.Persistence.SqlExtensionsStatics;
using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
+using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -20,21 +21,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// Limited to objects that have a corresponding node (in umbracoNode table).
/// Returns objects, i.e. lightweight representation of entities.
///
- internal class EntityRepository : IEntityRepository
+ internal class EntityRepository : RepositoryBase, IEntityRepository
{
- private readonly IScopeAccessor _scopeAccessor;
-
- public EntityRepository(IScopeAccessor scopeAccessor)
+ public EntityRepository(IScopeAccessor scopeAccessor, AppCaches appCaches)
+ : base(scopeAccessor, appCaches)
{
- _scopeAccessor = scopeAccessor;
}
- protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database;
- protected Sql Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql();
- protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
-
#region Repository
-
+
public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
IQuery filter, Ordering ordering)
{
@@ -49,17 +44,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
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 =>
+ Sql sql = GetBaseWhere(isContent, isMedia, isMember, false, s =>
{
sqlCustomization?.Invoke(s);
if (filter != null)
{
- foreach (var filterClause in filter.GetWhereClauses())
+ foreach (Tuple filterClause in filter.GetWhereClauses())
+ {
s.Where(filterClause.Item1, filterClause.Item2);
+ }
}
-
-
}, objectTypes);
ordering = ordering ?? Ordering.ByDefault();
@@ -75,7 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
// TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently
- //no matter what we always must have node id ordered at the end
+ // no matter what we always must have node id ordered at the end
sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId");
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
@@ -102,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEntitySlim GetEntity(Sql sql, bool isContent, bool isMedia, bool isMember)
{
- //isContent is going to return a 1:M result now with the variants so we need to do different things
+ // isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
var cdtos = Database.Fetch(sql);
@@ -164,7 +159,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
private IEnumerable GetEntities(Sql sql, bool isContent, bool isMedia, bool isMember)
{
- //isContent is going to return a 1:M result now with the variants so we need to do different things
+ // isContent is going to return a 1:M result now with the variants so we need to do different things
if (isContent)
{
var cdtos = Database.Fetch(sql);
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
similarity index 63%
rename from src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
rename to src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
index a9e8f4bb16..8f9c5102ab 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs
@@ -1,7 +1,8 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
+using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Querying;
@@ -9,53 +10,38 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
+
///
- /// Provides a base class to all repositories.
+ /// Provides a base class to all based repositories.
///
- /// The type of the entity managed by this repository.
/// The type of the entity's unique identifier.
- public abstract class RepositoryBase : IReadWriteQueryRepository
+ /// The type of the entity managed by this repository.
+ public abstract class EntityRepositoryBase : RepositoryBase, IReadWriteQueryRepository
where TEntity : class, IEntity
{
private IRepositoryCachePolicy _cachePolicy;
-
- protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger)
- {
- ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
- Logger = logger ?? throw new ArgumentNullException(nameof(logger));
- AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
- }
-
- protected ILogger> Logger { get; }
-
- protected AppCaches AppCaches { get; }
-
- protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate();
-
- protected IScopeAccessor ScopeAccessor { get; }
-
- protected IScope AmbientScope
- {
- get
- {
- var scope = ScopeAccessor.AmbientScope;
- if (scope == null)
- throw new InvalidOperationException("Cannot run a repository without an ambient scope.");
- return scope;
- }
- }
-
- #region Static Queries
-
private IQuery _hasIdQuery;
+ private static RepositoryCachePolicyOptions s_defaultOptions;
- #endregion
-
- protected virtual TId GetEntityId(TEntity entity)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger)
+ : base(scopeAccessor, appCaches)
{
- return (TId) (object) entity.Id;
+ Logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
+ ///
+ /// Gets the logger
+ ///
+ protected ILogger> Logger { get; }
+
+ ///
+ /// Gets the isolated cache for the
+ ///
+ protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate();
+
///
/// Gets the isolated cache.
///
@@ -78,30 +64,34 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
}
- // ReSharper disable once StaticMemberInGenericType
- private static RepositoryCachePolicyOptions _defaultOptions;
- // ReSharper disable once InconsistentNaming
- protected virtual RepositoryCachePolicyOptions DefaultOptions
- {
- get
- {
- return _defaultOptions ?? (_defaultOptions
+ ///
+ /// Gets the default
+ ///
+ protected virtual RepositoryCachePolicyOptions DefaultOptions => s_defaultOptions ?? (s_defaultOptions
= new RepositoryCachePolicyOptions(() =>
{
// get count of all entities of current type (TEntity) to ensure cached result is correct
// create query once if it is needed (no need for locking here) - query is static!
- var query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0));
+ IQuery query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query().Where(x => x.Id != 0));
return PerformCount(query);
}));
- }
- }
+ ///
+ /// Gets the node object type for the repository's entity
+ ///
+ protected abstract Guid NodeObjectTypeId { get; }
+
+ ///
+ /// Gets the repository cache policy
+ ///
protected IRepositoryCachePolicy CachePolicy
{
get
{
if (AppCaches == AppCaches.NoCache)
+ {
return NoCacheRepositoryCachePolicy.Instance;
+ }
// create the cache policy using IsolatedCache which is either global
// or scoped depending on the repository cache mode for the current scope
@@ -122,66 +112,102 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
}
+ ///
+ /// Get the entity id for the
+ ///
+ protected virtual TId GetEntityId(TEntity entity)
+ => (TId)(object)entity.Id;
+
+ ///
+ /// Create the repository cache policy
+ ///
protected virtual IRepositoryCachePolicy CreateCachePolicy()
- {
- return new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
- }
+ => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
///
/// Adds or Updates an entity of type TEntity
///
/// This method is backed by an cache
- ///
public virtual void Save(TEntity entity)
{
if (entity.HasIdentity == false)
+ {
CachePolicy.Create(entity, PersistNewItem);
+ }
else
+ {
CachePolicy.Update(entity, PersistUpdatedItem);
+ }
}
///
/// Deletes the passed in entity
///
- ///
public virtual void Delete(TEntity entity)
- {
- CachePolicy.Delete(entity, PersistDeletedItem);
- }
+ => CachePolicy.Delete(entity, PersistDeletedItem);
protected abstract TEntity PerformGet(TId id);
+
protected abstract IEnumerable PerformGetAll(params TId[] ids);
+
protected abstract IEnumerable PerformGetByQuery(IQuery query);
- protected abstract bool PerformExists(TId id);
- protected abstract int PerformCount(IQuery query);
protected abstract void PersistNewItem(TEntity item);
- protected abstract void PersistUpdatedItem(TEntity item);
- protected abstract void PersistDeletedItem(TEntity item);
+ protected abstract void PersistUpdatedItem(TEntity item);
+
+ // TODO: obsolete, use QueryType instead everywhere like GetBaseQuery(QueryType queryType);
+ protected abstract Sql GetBaseQuery(bool isCount);
+
+ protected abstract string GetBaseWhereClause();
+
+ protected abstract IEnumerable GetDeleteClauses();
+
+ protected virtual bool PerformExists(TId id)
+ {
+ var sql = GetBaseQuery(true);
+ sql.Where(GetBaseWhereClause(), new { id = id });
+ var count = Database.ExecuteScalar(sql);
+ return count == 1;
+ }
+
+ protected virtual int PerformCount(IQuery query)
+ {
+ var sqlClause = GetBaseQuery(true);
+ var translator = new SqlTranslator(sqlClause, query);
+ var sql = translator.Translate();
+
+ return Database.ExecuteScalar(sql);
+ }
+
+ protected virtual void PersistDeletedItem(TEntity entity)
+ {
+ var deletes = GetDeleteClauses();
+ foreach (var delete in deletes)
+ {
+ Database.Execute(delete, new { id = GetEntityId(entity) });
+ }
+
+ entity.DeleteDate = DateTime.Now;
+ }
///
/// Gets an entity by the passed in Id utilizing the repository's cache policy
///
- ///
- ///
public TEntity Get(TId id)
- {
- return CachePolicy.Get(id, PerformGet, PerformGetAll);
- }
+ => CachePolicy.Get(id, PerformGet, PerformGetAll);
///
/// Gets all entities of type TEntity or a list according to the passed in Ids
///
- ///
- ///
public IEnumerable GetMany(params TId[] ids)
{
- //ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
+ // ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
ids = ids.Distinct()
- //don't query by anything that is a default of T (like a zero)
+
+ // don't query by anything that is a default of T (like a zero)
// TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids
- //.Where(x => Equals(x, default(TId)) == false)
+ // .Where(x => Equals(x, default(TId)) == false)
.ToArray();
// can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
@@ -197,39 +223,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
}
+
return entities;
}
///
/// Gets a list of entities by the passed in query
///
- ///
- ///
public IEnumerable Get(IQuery query)
- {
- return PerformGetByQuery(query)
- //ensure we don't include any null refs in the returned collection!
- .WhereNotNull();
- }
+ => PerformGetByQuery(query)
+ .WhereNotNull(); // ensure we don't include any null refs in the returned collection!
///
/// Returns a boolean indicating whether an entity with the passed Id exists
///
- ///
- ///
public bool Exists(TId id)
- {
- return CachePolicy.Exists(id, PerformExists, PerformGetAll);
- }
+ => CachePolicy.Exists(id, PerformExists, PerformGetAll);
///
/// Returns an integer with the count of entities found with the passed in query
///
- ///
- ///
public int Count(IQuery query)
- {
- return PerformCount(query);
- }
+ => PerformCount(query);
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
index c3ed111ffb..29cbdf04e5 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// TODO: We should update this to support both users and members. It means we would remove referential integrity from users
// and the user/member key would be a GUID (we also need to add a GUID to users)
- internal class ExternalLoginRepository : NPocoRepositoryBase, IExternalLoginRepository
+ internal class ExternalLoginRepository : EntityRepositoryBase, IExternalLoginRepository
{
public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs
index eb55b476c7..ba3754486c 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs
@@ -12,7 +12,7 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class KeyValueRepository : NPocoRepositoryBase, IKeyValueRepository
+ internal class KeyValueRepository : EntityRepositoryBase, IKeyValueRepository
{
public KeyValueRepository(IScopeAccessor scopeAccessor, ILogger logger)
: base(scopeAccessor, AppCaches.NoCache, logger)
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Guid NodeObjectTypeId => throw new NotSupportedException();
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs
index fd791fe01f..bd72a3faf5 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents a repository for doing CRUD operations for
///
- internal class LanguageRepository : NPocoRepositoryBase, ILanguageRepository
+ internal class LanguageRepository : EntityRepositoryBase, ILanguageRepository
{
private readonly GlobalSettings _globalSettings;
private readonly Dictionary _codeIdMap = new Dictionary(StringComparer.OrdinalIgnoreCase);
@@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs
index 61dad47378..678f826fb4 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MacroRepository.cs
@@ -14,7 +14,7 @@ using Umbraco.Core.Strings;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class MacroRepository : NPocoRepositoryBase, IMacroRepository
+ internal class MacroRepository : EntityRepositoryBase, IMacroRepository
{
private readonly IShortStringHelper _shortStringHelper;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs
index 0ebea656b1..7e3425707a 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs
@@ -412,7 +412,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things!
/// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them
///
- private class MediaByGuidReadRepository : NPocoRepositoryBase
+ private class MediaByGuidReadRepository : EntityRepositoryBase
{
private readonly MediaRepository _outerRepo;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs
index 482a0e627f..6916203e93 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs
@@ -14,7 +14,7 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class MemberGroupRepository : NPocoRepositoryBase, IMemberGroupRepository
+ internal class MemberGroupRepository : EntityRepositoryBase, IMemberGroupRepository
{
public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs
deleted file mode 100644
index 392e7bdf1f..0000000000
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.Collections.Generic;
-using NPoco;
-using Microsoft.Extensions.Logging;
-using Umbraco.Core.Cache;
-using Umbraco.Core.Models.Entities;
-using Umbraco.Core.Persistence.Querying;
-using Umbraco.Core.Persistence.SqlSyntax;
-using Umbraco.Core.Scoping;
-
-namespace Umbraco.Core.Persistence.Repositories.Implement
-{
- ///
- /// Represent an abstract Repository for NPoco based repositories
- ///
- ///
- ///
- public abstract class NPocoRepositoryBase : RepositoryBase
- where TEntity : class, IEntity
- {
- ///
- /// Initializes a new instance of the class.
- ///
- protected NPocoRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger)
- : base(scopeAccessor, cache, logger)
- { }
-
- ///
- /// Gets the repository's database.
- ///
- protected IUmbracoDatabase Database => AmbientScope.Database;
-
- ///
- /// Gets the Sql context.
- ///
- protected ISqlContext SqlContext=> AmbientScope.SqlContext;
-
- protected Sql Sql() => SqlContext.Sql();
- protected Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args);
- protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
- protected IQuery Query() => SqlContext.Query();
-
- #region Abstract Methods
-
- protected abstract Sql GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere
- protected abstract string GetBaseWhereClause();
- protected abstract IEnumerable GetDeleteClauses();
- protected abstract Guid NodeObjectTypeId { get; }
- protected abstract override void PersistNewItem(TEntity entity);
- protected abstract override void PersistUpdatedItem(TEntity entity);
-
- #endregion
-
- protected override bool PerformExists(TId id)
- {
- var sql = GetBaseQuery(true);
- sql.Where(GetBaseWhereClause(), new { id = id});
- var count = Database.ExecuteScalar(sql);
- return count == 1;
- }
-
- protected override int PerformCount(IQuery query)
- {
- var sqlClause = GetBaseQuery(true);
- var translator = new SqlTranslator(sqlClause, query);
- var sql = translator.Translate();
-
- return Database.ExecuteScalar(sql);
- }
-
- protected override void PersistDeletedItem(TEntity entity)
- {
- var deletes = GetDeleteClauses();
- foreach (var delete in deletes)
- {
- Database.Execute(delete, new { id = GetEntityId(entity) });
- }
- entity.DeleteDate = DateTime.Now;
- }
- }
-}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs
index 279a7075ea..161de8c58e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs
@@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// This repo implements the base class so that permissions can be queued to be persisted
/// like the normal repository pattern but the standard repository Get commands don't apply and will throw
///
- internal class PermissionRepository : NPocoRepositoryBase
+ internal class PermissionRepository : EntityRepositoryBase
where TEntity : class, IEntity
{
public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs
index 6d2f95bb4d..5730272dd9 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs
@@ -13,7 +13,7 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class PublicAccessRepository : NPocoRepositoryBase, IPublicAccessRepository
+ internal class PublicAccessRepository : EntityRepositoryBase, IPublicAccessRepository
{
public PublicAccessRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
index 9e72846b58..246adf7415 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs
@@ -12,7 +12,7 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class RedirectUrlRepository : NPocoRepositoryBase, IRedirectUrlRepository
+ internal class RedirectUrlRepository : EntityRepositoryBase, IRedirectUrlRepository
{
public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
index 21b4ce5911..4299d50f15 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents a repository for doing CRUD operations for
///
- internal class RelationRepository : NPocoRepositoryBase, IRelationRepository
+ internal class RelationRepository : EntityRepositoryBase, IRelationRepository
{
private readonly IRelationTypeRepository _relationTypeRepository;
private readonly IEntityRepository _entityRepository;
@@ -88,7 +88,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs
index 398dd225ba..953999eaf2 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents a repository for doing CRUD operations for
///
- internal class RelationTypeRepository : NPocoRepositoryBase, IRelationTypeRepository
+ internal class RelationTypeRepository : EntityRepositoryBase, IRelationTypeRepository
{
public RelationTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
@@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs
new file mode 100644
index 0000000000..8b9d8fe77c
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBase.cs
@@ -0,0 +1,81 @@
+using System;
+using NPoco;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Core.Scoping;
+
+namespace Umbraco.Core.Persistence.Repositories.Implement
+{
+ ///
+ /// Base repository class for all instances
+ ///
+ public abstract class RepositoryBase : IRepository
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches)
+ {
+ ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
+ AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
+ }
+
+ ///
+ /// Gets the
+ ///
+ protected AppCaches AppCaches { get; }
+
+ ///
+ /// Gets the
+ ///
+ protected IScopeAccessor ScopeAccessor { get; }
+
+ ///
+ /// Gets the AmbientScope
+ ///
+ protected IScope AmbientScope
+ {
+ get
+ {
+ IScope scope = ScopeAccessor.AmbientScope;
+ if (scope == null)
+ {
+ throw new InvalidOperationException("Cannot run a repository without an ambient scope.");
+ }
+
+ return scope;
+ }
+ }
+
+ ///
+ /// Gets the repository's database.
+ ///
+ protected IUmbracoDatabase Database => AmbientScope.Database;
+
+ ///
+ /// Gets the Sql context.
+ ///
+ protected ISqlContext SqlContext => AmbientScope.SqlContext;
+
+ ///
+ /// Gets the
+ ///
+ protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
+
+ ///
+ /// Creates an expression
+ ///
+ protected Sql Sql() => SqlContext.Sql();
+
+ ///
+ /// Creates a expression
+ ///
+ protected Sql Sql(string sql, params object[] args) => SqlContext.Sql(sql, args);
+
+ ///
+ /// Creates a new query expression
+ ///
+ protected IQuery Query() => SqlContext.Query();
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs
index f215a8997b..556f837245 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs
@@ -13,7 +13,7 @@ using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class ServerRegistrationRepository : NPocoRepositoryBase, IServerRegistrationRepository
+ internal class ServerRegistrationRepository : EntityRepositoryBase, IServerRegistrationRepository
{
public ServerRegistrationRepository(IScopeAccessor scopeAccessor, ILogger logger)
: base(scopeAccessor, AppCaches.NoCache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs
index bbe751d2c6..9ddb5c5b60 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Simple abstract ReadOnly repository used to simply have PerformGet and PeformGetAll with an underlying cache
///
- internal abstract class SimpleGetRepository : NPocoRepositoryBase
+ internal abstract class SimpleGetRepository : EntityRepositoryBase
where TEntity : class, IEntity
where TDto: class
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
index dcd9464ae0..94c2f4289a 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs
@@ -15,7 +15,7 @@ using static Umbraco.Core.Persistence.SqlExtensionsStatics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
- internal class TagRepository : NPocoRepositoryBase, ITagRepository
+ internal class TagRepository : EntityRepositoryBase, ITagRepository
{
public TagRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger)
: base(scopeAccessor, cache, logger)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs
index b36474d688..d391bb9e4d 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents the Template Repository
///
- internal class TemplateRepository : NPocoRepositoryBase, ITemplateRepository
+ internal class TemplateRepository : EntityRepositoryBase, ITemplateRepository
{
private readonly IIOHelper _ioHelper;
private readonly IShortStringHelper _shortStringHelper;
@@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs
index 30b9b29416..4786548e57 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs
@@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents the UserGroupRepository for doing CRUD operations for
///
- public class UserGroupRepository : NPocoRepositoryBase, IUserGroupRepository
+ public class UserGroupRepository : EntityRepositoryBase, IUserGroupRepository
{
private readonly IShortStringHelper _shortStringHelper;
private readonly UserGroupWithUsersRepository _userGroupWithUsersRepository;
@@ -216,7 +216,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected Sql GetBaseQuery(QueryType type)
{
@@ -358,7 +358,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// used to persist a user group with associated users at once
///
- private class UserGroupWithUsersRepository : NPocoRepositoryBase
+ private class UserGroupWithUsersRepository : EntityRepositoryBase
{
private readonly UserGroupRepository _userGroupRepo;
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
index 51000dbe70..1557dcc1d1 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represents the UserRepository for doing CRUD operations for
///
- internal class UserRepository : NPocoRepositoryBase, IUserRepository
+ internal class UserRepository : EntityRepositoryBase, IUserRepository
{
private readonly IMapperCollection _mapperCollection;
private readonly GlobalSettings _globalSettings;
@@ -376,7 +376,7 @@ ORDER BY colName";
#endregion
- #region Overrides of NPocoRepositoryBase
+ #region Overrides of EntityRepositoryBase
protected override Sql GetBaseQuery(bool isCount)
{
diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
index f46c118174..f35f9b9469 100644
--- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
+++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/BlockListPropertyValueConverter.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@@ -103,8 +103,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
if (settingGuidUdi != null)
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
- if (!contentData.ContentType.TryGetKey(out var contentTypeKey))
- throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2));
+ var contentTypeKey = contentData.ContentType.Key;
if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig))
continue;
@@ -113,8 +112,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
// we also ensure that the content type's match since maybe the settings type has been changed after this has been persisted.
if (settingsData != null)
{
- if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey))
- throw new InvalidOperationException("The settings element type was not of type " + typeof(IPublishedContentType2));
+ var settingsElementTypeKey = settingsData.ContentType.Key;
if (!blockConfig.SettingsElementTypeKey.HasValue || settingsElementTypeKey != blockConfig.SettingsElementTypeKey)
settingsData = null;
diff --git a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs
index 4c1482c82c..ae99243a2c 100644
--- a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs
+++ b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -190,8 +190,7 @@ namespace Umbraco.Web.PublishedCache
try
{
_lock.EnterWriteLock();
- if (type.TryGetKey(out var key))
- _keyToIdMap[key] = type.Id;
+ _keyToIdMap[type.Key] = type.Id;
return _typesByAlias[aliasKey] = _typesById[type.Id] = type;
}
finally
@@ -227,8 +226,7 @@ namespace Umbraco.Web.PublishedCache
try
{
_lock.EnterWriteLock();
- if (type.TryGetKey(out var key))
- _keyToIdMap[key] = type.Id;
+ _keyToIdMap[type.Key] = type.Id;
return _typesByAlias[GetAliasKey(type)] = _typesById[type.Id] = type;
}
finally
diff --git a/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs b/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
new file mode 100644
index 0000000000..b67f41136a
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/AppPluginsManifestWatcherNotificationHandler.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Umbraco.Core;
+using Umbraco.Core.Events;
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Manifest;
+
+namespace Umbraco.Infrastructure.Runtime
+{
+ ///
+ /// Starts monitoring AppPlugins directory during debug runs, to restart site when a plugin manifest changes.
+ ///
+ public sealed class AppPluginsManifestWatcherNotificationHandler : INotificationHandler
+ {
+ private readonly ManifestWatcher _manifestWatcher;
+ private readonly IHostingEnvironment _hostingEnvironment;
+
+ public AppPluginsManifestWatcherNotificationHandler(ManifestWatcher manifestWatcher, IHostingEnvironment hostingEnvironment)
+ {
+ _manifestWatcher = manifestWatcher ?? throw new ArgumentNullException(nameof(manifestWatcher));
+ _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
+ }
+
+ public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
+ {
+ if (!_hostingEnvironment.IsDebugMode)
+ {
+ return Task.CompletedTask;
+ }
+
+ var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
+
+ if (!Directory.Exists(appPlugins))
+ {
+ return Task.CompletedTask;
+ }
+
+ _manifestWatcher.Start(Directory.GetDirectories(appPlugins));
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs b/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs
similarity index 96%
rename from src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
rename to src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs
index 126d235ae0..32f5e6bf36 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComposer.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreInitialServices.cs
@@ -1,10 +1,10 @@
-using System;
+using System;
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Umbraco.Core;
using Umbraco.Core.Cache;
-using Umbraco.Core.Composing;
using Umbraco.Core.Composing.CompositionExtensions;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
@@ -62,15 +62,16 @@ using Umbraco.Web.Templates;
using Umbraco.Web.Trees;
using TextStringValueConverter = Umbraco.Core.PropertyEditors.ValueConverters.TextStringValueConverter;
-namespace Umbraco.Core.Runtime
+namespace Umbraco.Infrastructure.Runtime
{
- // core's initial composer composes before all core composers
- [ComposeBefore(typeof(ICoreComposer))]
- public class CoreInitialComposer : ComponentComposer
+ public static class CoreInitialServices
{
- public override void Compose(IUmbracoBuilder builder)
+ public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder)
{
- base.Compose(builder);
+ builder.AddNotificationHandler();
+
+ builder.Services.AddSingleton();
+ builder.AddNotificationHandler();
// composers
builder
@@ -302,7 +303,7 @@ namespace Umbraco.Core.Runtime
// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddUnique();
builder.HealthChecks()
- .Add(() => builder.TypeLoader.GetTypes());
+ .Add(() => builder.TypeLoader.GetTypes());
builder.WithCollectionBuilder()
.Add(() => builder.TypeLoader.GetTypes());
@@ -382,7 +383,10 @@ namespace Umbraco.Core.Runtime
builder.Services.AddUnique();
builder.Services.AddUnique();
+
builder.Services.AddUnique();
+
+ return builder;
}
}
}
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index cb02a90ebe..a05c1a7f98 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -1,7 +1,10 @@
-using System;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Composing;
+using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
@@ -10,71 +13,103 @@ namespace Umbraco.Infrastructure.Runtime
{
public class CoreRuntime : IRuntime
{
- public IRuntimeState State { get; }
-
private readonly ILogger _logger;
+ private readonly ILoggerFactory _loggerFactory;
private readonly ComponentCollection _components;
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
private readonly IProfilingLogger _profilingLogger;
private readonly IMainDom _mainDom;
private readonly IUmbracoDatabaseFactory _databaseFactory;
+ private readonly IEventAggregator _eventAggregator;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ ///
+ /// Initializes a new instance of the class.
+ ///
public CoreRuntime(
- ILogger logger,
+ ILoggerFactory loggerFactory,
IRuntimeState state,
ComponentCollection components,
IApplicationShutdownRegistry applicationShutdownRegistry,
IProfilingLogger profilingLogger,
IMainDom mainDom,
- IUmbracoDatabaseFactory databaseFactory)
+ IUmbracoDatabaseFactory databaseFactory,
+ IEventAggregator eventAggregator,
+ IHostingEnvironment hostingEnvironment)
{
State = state;
- _logger = logger;
+ _loggerFactory = loggerFactory;
_components = components;
_applicationShutdownRegistry = applicationShutdownRegistry;
_profilingLogger = profilingLogger;
_mainDom = mainDom;
_databaseFactory = databaseFactory;
+ _eventAggregator = eventAggregator;
+ _hostingEnvironment = hostingEnvironment;
+ _logger = _loggerFactory.CreateLogger();
}
-
- public void Start()
+ ///
+ /// Gets the state of the Umbraco runtime.
+ ///
+ public IRuntimeState State { get; }
+
+ ///
+ public async Task StartAsync(CancellationToken cancellationToken)
{
+ StaticApplicationLogging.Initialize(_loggerFactory);
+
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
var exception = (Exception)args.ExceptionObject;
var isTerminating = args.IsTerminating; // always true?
var msg = "Unhandled exception in AppDomain";
- if (isTerminating) msg += " (terminating)";
+
+ if (isTerminating)
+ {
+ msg += " (terminating)";
+ }
+
msg += ".";
+
_logger.LogError(exception, msg);
};
+ AppDomain.CurrentDomain.SetData("DataDirectory", _hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data));
+
DetermineRuntimeLevel();
if (State.Level <= RuntimeLevel.BootFailed)
+ {
throw new InvalidOperationException($"Cannot start the runtime if the runtime level is less than or equal to {RuntimeLevel.BootFailed}");
+ }
- var hostingEnvironmentLifetime = _applicationShutdownRegistry;
+ IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry;
if (hostingEnvironmentLifetime == null)
- throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(Start)}");
+ {
+ throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(StartAsync)}");
+ }
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom();
+ await _eventAggregator.PublishAsync(new UmbracoApplicationStarting(State.Level), cancellationToken);
+
// create & initialize the components
_components.Initialize();
}
- public void Terminate()
+ public async Task StopAsync(CancellationToken cancellationToken)
{
_components.Terminate();
+ await _eventAggregator.PublishAsync(new UmbracoApplicationStopping(), cancellationToken);
+ StaticApplicationLogging.Initialize(null);
}
private void AcquireMainDom()
{
- using (var timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."))
+ using (DisposableTimer timer = _profilingLogger.DebugDuration("Acquiring MainDom.", "Acquired."))
{
try
{
@@ -90,7 +125,7 @@ namespace Umbraco.Infrastructure.Runtime
private void DetermineRuntimeLevel()
{
- using var timer = _profilingLogger.DebugDuration("Determining runtime level.", "Determined.");
+ using DisposableTimer timer = _profilingLogger.DebugDuration("Determining runtime level.", "Determined.");
try
{
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreInitialComponent.cs b/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
similarity index 67%
rename from src/Umbraco.Infrastructure/Runtime/CoreInitialComponent.cs
rename to src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
index 73318208b6..5543662464 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreInitialComponent.cs
+++ b/src/Umbraco.Infrastructure/Runtime/EssentialDirectoryCreator.cs
@@ -1,25 +1,28 @@
-using Microsoft.Extensions.Options;
-using Umbraco.Core.Composing;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
+using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
-namespace Umbraco.Core.Runtime
+namespace Umbraco.Infrastructure.Runtime
{
- public class CoreInitialComponent : IComponent
+ public class EssentialDirectoryCreator : INotificationHandler
{
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly GlobalSettings _globalSettings;
- public CoreInitialComponent(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions globalSettings)
+ public EssentialDirectoryCreator(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions globalSettings)
{
_ioHelper = ioHelper;
_hostingEnvironment = hostingEnvironment;
_globalSettings = globalSettings.Value;
}
- public void Initialize()
+ public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
{
// ensure we have some essential directories
// every other component can then initialize safely
@@ -28,9 +31,8 @@ namespace Umbraco.Core.Runtime
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials));
- }
- public void Terminate()
- { }
+ return Task.CompletedTask;
+ }
}
}
diff --git a/src/Umbraco.Infrastructure/Scoping/Scope.cs b/src/Umbraco.Infrastructure/Scoping/Scope.cs
index 65e8e343f7..84945c78d4 100644
--- a/src/Umbraco.Infrastructure/Scoping/Scope.cs
+++ b/src/Umbraco.Infrastructure/Scoping/Scope.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Data;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Cache;
@@ -20,7 +20,6 @@ namespace Umbraco.Core.Scoping
private readonly CoreDebugSettings _coreDebugSettings;
private readonly IMediaFileSystem _mediaFileSystem;
private readonly ILogger _logger;
- private readonly ITypeFinder _typeFinder;
private readonly IsolationLevel _isolationLevel;
private readonly RepositoryCacheMode _repositoryCacheMode;
@@ -38,10 +37,15 @@ namespace Umbraco.Core.Scoping
private IEventDispatcher _eventDispatcher;
// initializes a new scope
- private Scope(ScopeProvider scopeProvider,
+ private Scope(
+ ScopeProvider scopeProvider,
CoreDebugSettings coreDebugSettings,
IMediaFileSystem mediaFileSystem,
- ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IScopeContext scopeContext, bool detachable,
+ ILogger logger,
+ FileSystems fileSystems,
+ Scope parent,
+ IScopeContext scopeContext,
+ bool detachable,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
IEventDispatcher eventDispatcher = null,
@@ -53,7 +57,6 @@ namespace Umbraco.Core.Scoping
_coreDebugSettings = coreDebugSettings;
_mediaFileSystem = mediaFileSystem;
_logger = logger;
- _typeFinder = typeFinder;
Context = scopeContext;
@@ -117,31 +120,38 @@ namespace Umbraco.Core.Scoping
}
// initializes a new scope
- public Scope(ScopeProvider scopeProvider,
+ public Scope(
+ ScopeProvider scopeProvider,
CoreDebugSettings coreDebugSettings,
IMediaFileSystem mediaFileSystem,
- ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, IScopeContext scopeContext,
+ ILogger logger,
+ FileSystems fileSystems,
+ bool detachable,
+ IScopeContext scopeContext,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false,
bool autoComplete = false)
- : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
+ : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
{ }
// initializes a new scope in a nested scopes chain, with its parent
- public Scope(ScopeProvider scopeProvider,
+ public Scope(
+ ScopeProvider scopeProvider,
CoreDebugSettings coreDebugSettings,
IMediaFileSystem mediaFileSystem,
- ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent,
+ ILogger logger,
+ FileSystems fileSystems,
+ Scope parent,
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null,
bool callContext = false,
bool autoComplete = false)
- : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
+ : this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
{ }
public Guid InstanceId { get; } = Guid.NewGuid();
diff --git a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs
index 52c096b224..151c4cfb3c 100644
--- a/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs
+++ b/src/Umbraco.Infrastructure/Scoping/ScopeProvider.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Data;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -23,13 +23,12 @@ namespace Umbraco.Core.Scoping
{
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
- private readonly ITypeFinder _typeFinder;
private readonly IRequestCache _requestCache;
private readonly FileSystems _fileSystems;
private readonly CoreDebugSettings _coreDebugSettings;
private readonly IMediaFileSystem _mediaFileSystem;
- public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, ITypeFinder typeFinder, IRequestCache requestCache)
+ public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger logger, ILoggerFactory loggerFactory, IRequestCache requestCache)
{
DatabaseFactory = databaseFactory;
_fileSystems = fileSystems;
@@ -37,7 +36,6 @@ namespace Umbraco.Core.Scoping
_mediaFileSystem = mediaFileSystem;
_logger = logger;
_loggerFactory = loggerFactory;
- _typeFinder = typeFinder;
_requestCache = requestCache;
// take control of the FileSystems
_fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
@@ -256,7 +254,7 @@ namespace Umbraco.Core.Scoping
IEventDispatcher eventDispatcher = null,
bool? scopeFileSystems = null)
{
- return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems);
+ return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems);
}
///
@@ -312,13 +310,13 @@ namespace Umbraco.Core.Scoping
{
var ambientContext = AmbientContext;
var newContext = ambientContext == null ? new ScopeContext() : null;
- var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
+ var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
// assign only if scope creation did not throw!
SetAmbient(scope, newContext ?? ambientContext);
return scope;
}
- var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
+ var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
SetAmbient(nested, AmbientContext);
return nested;
}
diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs
index 3241fa9d0e..be541486ff 100644
--- a/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs
+++ b/src/Umbraco.Infrastructure/Services/Implement/ContentTypeServiceBaseOfTItemTService.cs
@@ -1,4 +1,4 @@
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Scoping;
@@ -16,10 +16,23 @@ namespace Umbraco.Core.Services.Implement
protected abstract TService This { get; }
- // that one must be dispatched
+ ///
+ /// Raised when a is changed
+ ///
+ ///
+ /// This event is dispatched after the trans is completed. Used by event refreshers.
+ ///
public static event TypedEventHandler.EventArgs> Changed;
- // that one is always immediate (transactional)
+ ///
+ /// Occurs when an is created or updated from within the (transaction)
+ ///
+ ///
+ /// The purpose of this event being raised within the transaction is so that listeners can perform database
+ /// operations from within the same transaction and guarantee data consistency so that if anything goes wrong
+ /// the entire transaction can be rolled back. This is used by Nucache.
+ /// TODO: See remarks in ContentRepositoryBase about these types of events.
+ ///
public static event TypedEventHandler.EventArgs> ScopedRefreshedEntity;
// used by tests to clear events
@@ -45,9 +58,11 @@ namespace Umbraco.Core.Services.Implement
scope.Events.Dispatch(Changed, This, args, nameof(Changed));
}
+ ///
+ /// Raises the event during the (transaction)
+ ///
protected void OnUowRefreshedEntity(ContentTypeChange.EventArgs args)
{
- // that one is always immediate (not dispatched, transactional)
ScopedRefreshedEntity.RaiseEvent(args, This);
}
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
index 2031a23af5..f7fab098b0 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/DisabledModelsBuilderComponent.cs
@@ -1,9 +1,13 @@
-using Umbraco.Core.Composing;
+using Umbraco.Core.Composing;
using Umbraco.ModelsBuilder.Embedded.BackOffice;
using Umbraco.Web.Features;
namespace Umbraco.ModelsBuilder.Embedded.Compose
{
+ // TODO: This needs to die, see TODO in ModelsBuilderComposer. This is also no longer used in this netcore
+ // codebase. Potentially this could be changed to ext methods if necessary that could be used by end users who will
+ // install the community MB package to disable any built in MB stuff.
+
///
/// Special component used for when MB is disabled with the legacy MB is detected
///
diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
index 81869a9261..ca597a607b 100644
--- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
+++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
@@ -1,4 +1,4 @@
-using System.Linq;
+using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Configuration;
using Umbraco.Core;
@@ -11,7 +11,8 @@ using Umbraco.Core.DependencyInjection;
namespace Umbraco.ModelsBuilder.Embedded.Compose
{
- [ComposeBefore(typeof(IPublishedCacheComposer))]
+ // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there
+ // This needs to execute before the AddNuCache call
public sealed class ModelsBuilderComposer : ICoreComposer
{
public void Compose(IUmbracoBuilder builder)
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
index 7cdc7e72e1..f1c4f1d85b 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs
index d41ca344dc..bb03693adf 100644
--- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs
+++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@@ -1167,8 +1167,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
SetValueLocked(_contentTypesById, type.Id, type);
SetValueLocked(_contentTypesByAlias, type.Alias, type);
// ensure the key/id map is accurate
- if (type.TryGetKey(out var key))
- _contentTypeKeyToIdMap[key] = type.Id;
+ _contentTypeKeyToIdMap[type.Key] = type.Id;
}
// set a node (just the node, not the tree)
@@ -1353,7 +1352,9 @@ namespace Umbraco.Web.PublishedCache.NuCache
// reading _floorGen is safe if _collectTask is null
if (_collectTask == null && _collectAuto && _liveGen - _floorGen > CollectMinGenDelta)
+ {
CollectAsyncLocked();
+ }
return snapshot;
}
@@ -1375,8 +1376,17 @@ namespace Umbraco.Web.PublishedCache.NuCache
private Task CollectAsyncLocked()
{
+ // NOTE: What in the heck is going on here? Why is any of this running in async contexts?
+ // SD: From what I can tell this was designed to be a set and forget background task to do the
+ // collecting which is why it's called from non-async methods within this class. This is
+ // slightly dangerous because it's not taking into account app shutdown.
+ // TODO: There should be a different method or class responsible for executing the cleanup on a
+ // background (set and forget) thread.
+
if (_collectTask != null)
+ {
return _collectTask;
+ }
// ReSharper disable InconsistentlySynchronizedField
var task = _collectTask = Task.Run((Action)Collect);
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs
index ec5424ad9a..98d423680b 100644
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs
+++ b/src/Umbraco.PublishedCache.NuCache/DataSource/ContentNestedData.cs
@@ -1,4 +1,4 @@
-using Newtonsoft.Json;
+using Newtonsoft.Json;
using System.Collections.Generic;
using Umbraco.Core.Serialization;
@@ -9,7 +9,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
///
internal class ContentNestedData
{
- //dont serialize empty properties
+ // dont serialize empty properties
[JsonProperty("pd")]
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter))]
public Dictionary PropertyData { get; set; }
@@ -21,7 +21,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
[JsonProperty("us")]
public string UrlSegment { get; set; }
- //Legacy properties used to deserialize existing nucache db entries
+ // Legacy properties used to deserialize existing nucache db entries
[JsonProperty("properties")]
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter))]
private Dictionary LegacyPropertyData { set { PropertyData = value; } }
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs
deleted file mode 100644
index bdcd8fe3e3..0000000000
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/DatabaseDataSource.cs
+++ /dev/null
@@ -1,325 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
-using NPoco;
-using Umbraco.Core;
-using Umbraco.Core.Persistence;
-using Umbraco.Core.Persistence.Dtos;
-using Umbraco.Core.Scoping;
-using Umbraco.Core.Serialization;
-using static Umbraco.Core.Persistence.SqlExtensionsStatics;
-
-namespace Umbraco.Web.PublishedCache.NuCache.DataSource
-{
- // TODO: use SqlTemplate for these queries else it's going to be horribly slow!
-
- // provides efficient database access for NuCache
- internal class DatabaseDataSource : IDataSource
- {
- private const int PageSize = 500;
-
- private readonly ILogger _logger;
-
- public DatabaseDataSource(ILogger logger)
- {
- _logger = logger ?? throw new ArgumentNullException(nameof(logger));
- }
-
- // we want arrays, we want them all loaded, not an enumerable
-
- private Sql ContentSourcesSelect(IScope scope, Func, Sql> joins = null)
- {
- var sql = scope.SqlContext.Sql()
-
- .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"),
- x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"),
- x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId"))
- .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId"))
- .AndSelect(x => Alias(x.Published, "Published"), x => Alias(x.Edited, "Edited"))
-
- .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId"))
- .AndSelect(x => Alias(x.TemplateId, "EditTemplateId"))
-
- .AndSelect("pcver", x => Alias(x.Id, "PublishedVersionId"), x => Alias(x.Text, "PubName"), x => Alias(x.VersionDate, "PubVersionDate"), x => Alias(x.UserId, "PubWriterId"))
- .AndSelect("pdver", x => Alias(x.TemplateId, "PubTemplateId"))
-
- .AndSelect("nuEdit", x => Alias(x.Data, "EditData"))
- .AndSelect("nuPub", x => Alias(x.Data, "PubData"))
-
- .From();
-
- if (joins != null)
- sql = joins(sql);
-
- sql = sql
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
-
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current)
- .InnerJoin().On((left, right) => left.Id == right.Id)
-
- .LeftJoin(j =>
- j.InnerJoin("pdver").On((left, right) => left.Id == right.Id && right.Published, "pcver", "pdver"), "pcver")
- .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcver")
-
- .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit")
- .LeftJoin("nuPub").On((left, right) => left.NodeId == right.NodeId && right.Published, aliasRight: "nuPub");
-
- return sql;
- }
-
- public ContentNodeKit GetContentSource(IScope scope, int id)
- {
- var sql = ContentSourcesSelect(scope)
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && x.NodeId == id && !x.Trashed)
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- var dto = scope.Database.Fetch(sql).FirstOrDefault();
- return dto == null ? new ContentNodeKit() : CreateContentNodeKit(dto);
- }
-
- public IEnumerable GetAllContentSources(IScope scope)
- {
- var sql = ContentSourcesSelect(scope)
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
- // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
-
- foreach (var row in scope.Database.QueryPaged(PageSize, sql))
- yield return CreateContentNodeKit(row);
- }
-
- public IEnumerable GetBranchContentSources(IScope scope, int id)
- {
- var syntax = scope.SqlContext.SqlSyntax;
- var sql = ContentSourcesSelect(scope,
- s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x"))
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
- .Where(x => x.NodeId == id, "x")
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
- // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
-
- foreach (var row in scope.Database.QueryPaged(PageSize, sql))
- yield return CreateContentNodeKit(row);
- }
-
- public IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids)
- {
- if (!ids.Any()) yield break;
-
- var sql = ContentSourcesSelect(scope)
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
- .WhereIn(x => x.ContentTypeId, ids)
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
- // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
-
- foreach (var row in scope.Database.QueryPaged(PageSize, sql))
- yield return CreateContentNodeKit(row);
- }
-
- private Sql MediaSourcesSelect(IScope scope, Func, Sql> joins = null)
- {
- var sql = scope.SqlContext.Sql()
-
- .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"),
- x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"),
- x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId"))
- .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId"))
- .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId"))
- .AndSelect("nuEdit", x => Alias(x.Data, "EditData"))
- .From();
-
- if (joins != null)
- sql = joins(sql);
-
- sql = sql
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
- .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current)
- .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit");
-
- return sql;
- }
-
- public ContentNodeKit GetMediaSource(IScope scope, int id)
- {
- var sql = MediaSourcesSelect(scope)
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && x.NodeId == id && !x.Trashed)
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- var dto = scope.Database.Fetch(sql).FirstOrDefault();
- return dto == null ? new ContentNodeKit() : CreateMediaNodeKit(dto);
- }
-
- public IEnumerable GetAllMediaSources(IScope scope)
- {
- var sql = MediaSourcesSelect(scope)
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed)
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
- // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
-
- foreach (var row in scope.Database.QueryPaged(PageSize, sql))
- yield return CreateMediaNodeKit(row);
- }
-
- public IEnumerable GetBranchMediaSources(IScope scope, int id)
- {
- var syntax = scope.SqlContext.SqlSyntax;
- var sql = MediaSourcesSelect(scope,
- s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x"))
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed)
- .Where(x => x.NodeId == id, "x")
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
- // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
-
- foreach (var row in scope.Database.QueryPaged(PageSize, sql))
- yield return CreateMediaNodeKit(row);
- }
-
- public IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids)
- {
- if (!ids.Any()) yield break;
-
- var sql = MediaSourcesSelect(scope)
- .Where(x => x.NodeObjectType == Constants.ObjectTypes.Media && !x.Trashed)
- .WhereIn(x => x.ContentTypeId, ids)
- .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
-
- // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
- // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
-
- foreach (var row in scope.Database.QueryPaged(PageSize, sql))
- yield return CreateMediaNodeKit(row);
- }
-
- private ContentNodeKit CreateContentNodeKit(ContentSourceDto dto)
- {
- ContentData d = null;
- ContentData p = null;
-
- if (dto.Edited)
- {
- if (dto.EditData == null)
- {
- if (Debugger.IsAttached)
- throw new InvalidOperationException("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding.");
- _logger.LogWarning("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id);
- }
- else
- {
- var nested = DeserializeNestedData(dto.EditData);
-
- d = new ContentData
- {
- Name = dto.EditName,
- Published = false,
- TemplateId = dto.EditTemplateId,
- VersionId = dto.VersionId,
- VersionDate = dto.EditVersionDate,
- WriterId = dto.EditWriterId,
- Properties = nested.PropertyData,
- CultureInfos = nested.CultureData,
- UrlSegment = nested.UrlSegment
- };
- }
- }
-
- if (dto.Published)
- {
- if (dto.PubData == null)
- {
- if (Debugger.IsAttached)
- throw new InvalidOperationException("Missing cmsContentNu published content for node " + dto.Id + ", consider rebuilding.");
- _logger.LogWarning("Missing cmsContentNu published content for node {NodeId}, consider rebuilding.", dto.Id);
- }
- else
- {
- var nested = DeserializeNestedData(dto.PubData);
-
- p = new ContentData
- {
- Name = dto.PubName,
- UrlSegment = nested.UrlSegment,
- Published = true,
- TemplateId = dto.PubTemplateId,
- VersionId = dto.VersionId,
- VersionDate = dto.PubVersionDate,
- WriterId = dto.PubWriterId,
- Properties = nested.PropertyData,
- CultureInfos = nested.CultureData
- };
- }
- }
-
- var n = new ContentNode(dto.Id, dto.Uid,
- dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId);
-
- var s = new ContentNodeKit
- {
- Node = n,
- ContentTypeId = dto.ContentTypeId,
- DraftData = d,
- PublishedData = p
- };
-
- return s;
- }
-
- private static ContentNodeKit CreateMediaNodeKit(ContentSourceDto dto)
- {
- if (dto.EditData == null)
- throw new InvalidOperationException("No data for media " + dto.Id);
-
- var nested = DeserializeNestedData(dto.EditData);
-
- var p = new ContentData
- {
- Name = dto.EditName,
- Published = true,
- TemplateId = -1,
- VersionId = dto.VersionId,
- VersionDate = dto.EditVersionDate,
- WriterId = dto.CreatorId, // what-else?
- Properties = nested.PropertyData,
- CultureInfos = nested.CultureData
- };
-
- var n = new ContentNode(dto.Id, dto.Uid,
- dto.Level, dto.Path, dto.SortOrder, dto.ParentId, dto.CreateDate, dto.CreatorId);
-
- var s = new ContentNodeKit
- {
- Node = n,
- ContentTypeId = dto.ContentTypeId,
- PublishedData = p
- };
-
- return s;
- }
-
- private static ContentNestedData DeserializeNestedData(string data)
- {
- // by default JsonConvert will deserialize our numeric values as Int64
- // which is bad, because they were Int32 in the database - take care
-
- var settings = new JsonSerializerSettings
- {
- Converters = new List { new ForceInt32Converter() }
- };
-
- return JsonConvert.DeserializeObject(data, settings);
- }
- }
-}
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs
deleted file mode 100644
index ec3ab38e84..0000000000
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/IDataSource.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using System.Collections.Generic;
-using Umbraco.Core.Scoping;
-
-namespace Umbraco.Web.PublishedCache.NuCache.DataSource
-{
- ///
- /// Defines a data source for NuCache.
- ///
- internal interface IDataSource
- {
- //TODO: For these required sort orders, would sorting on Path 'just work'?
-
- ContentNodeKit GetContentSource(IScope scope, int id);
-
- ///
- /// Returns all content ordered by level + sortOrder
- ///
- ///
- ///
- ///
- /// MUST be ordered by level + parentId + sortOrder!
- ///
- IEnumerable GetAllContentSources(IScope scope);
-
- ///
- /// Returns branch for content ordered by level + sortOrder
- ///
- ///
- ///
- ///
- /// MUST be ordered by level + parentId + sortOrder!
- ///
- IEnumerable GetBranchContentSources(IScope scope, int id);
-
- ///
- /// Returns content by Ids ordered by level + sortOrder
- ///
- ///
- ///
- ///
- /// MUST be ordered by level + parentId + sortOrder!
- ///
- IEnumerable GetTypeContentSources(IScope scope, IEnumerable ids);
-
- ContentNodeKit GetMediaSource(IScope scope, int id);
-
- ///
- /// Returns all media ordered by level + sortOrder
- ///
- ///
- ///
- ///
- /// MUST be ordered by level + parentId + sortOrder!
- ///
- IEnumerable GetAllMediaSources(IScope scope);
-
- ///
- /// Returns branch for media ordered by level + sortOrder
- ///
- ///
- ///
- ///
- /// MUST be ordered by level + parentId + sortOrder!
- ///
- IEnumerable GetBranchMediaSources(IScope scope, int id); // must order by level, sortOrder
-
- ///
- /// Returns media by Ids ordered by level + sortOrder
- ///
- ///
- ///
- ///
- /// MUST be ordered by level + parentId + sortOrder!
- ///
- IEnumerable GetTypeMediaSources(IScope scope, IEnumerable ids);
- }
-}
diff --git a/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs b/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs
index cf7ab95360..42e038c744 100644
--- a/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs
+++ b/src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.ComponentModel;
using Newtonsoft.Json;
@@ -29,7 +29,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
public object Value { get; set; }
- //Legacy properties used to deserialize existing nucache db entries
+ // Legacy properties used to deserialize existing nucache db entries
[JsonProperty("culture")]
private string LegacyCulture
{
diff --git a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs
new file mode 100644
index 0000000000..719d014296
--- /dev/null
+++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -0,0 +1,59 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Umbraco.Core;
+using Umbraco.Core.DependencyInjection;
+using Umbraco.Core.Models;
+using Umbraco.Core.Scoping;
+using Umbraco.Core.Services;
+using Umbraco.Infrastructure.PublishedCache.Persistence;
+using Umbraco.Web.PublishedCache;
+using Umbraco.Web.PublishedCache.NuCache;
+
+namespace Umbraco.Infrastructure.PublishedCache.DependencyInjection
+{
+ ///
+ /// Extension methods for for the Umbraco's NuCache
+ ///
+ public static class UmbracoBuilderExtensions
+ {
+ ///
+ /// Adds Umbraco NuCache dependencies
+ ///
+ public static IUmbracoBuilder AddNuCache(this IUmbracoBuilder builder)
+ {
+ // register the NuCache database data source
+ builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
+
+ // register the NuCache published snapshot service
+ // must register default options, required in the service ctor
+ builder.Services.TryAddTransient(factory => new PublishedSnapshotServiceOptions());
+ builder.SetPublishedSnapshotService();
+
+ // Add as itself
+ builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
+
+ // replace this service since we want to improve the content/media
+ // mapping lookups if we are using nucache.
+ // TODO: Gotta wonder how much this does actually improve perf? It's a lot of weird code to make this happen so hope it's worth it
+ builder.Services.AddUnique(factory =>
+ {
+ var idkSvc = new IdKeyMap(factory.GetRequiredService());
+ if (factory.GetRequiredService() is PublishedSnapshotService publishedSnapshotService)
+ {
+ idkSvc.SetMapper(UmbracoObjectTypes.Document, id => publishedSnapshotService.GetDocumentUid(id), uid => publishedSnapshotService.GetDocumentId(uid));
+ idkSvc.SetMapper(UmbracoObjectTypes.Media, id => publishedSnapshotService.GetMediaUid(id), uid => publishedSnapshotService.GetMediaId(uid));
+ }
+
+ return idkSvc;
+ });
+
+ // add the NuCache health check (hidden from type finder)
+ // TODO: no NuCache health check yet
+ // composition.HealthChecks().Add();
+ return builder;
+ }
+ }
+}
diff --git a/src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs b/src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs
deleted file mode 100644
index fba133a2aa..0000000000
--- a/src/Umbraco.PublishedCache.NuCache/NuCacheComponent.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Umbraco.Core.Composing;
-
-namespace Umbraco.Web.PublishedCache.NuCache
-{
- public sealed class NuCacheComponent : IComponent
- {
- public NuCacheComponent(IPublishedSnapshotService service)
- {
- // nothing - this just ensures that the service is created at boot time
- }
-
- public void Initialize()
- { }
-
- public void Terminate()
- { }
- }
-}
diff --git a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs b/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs
deleted file mode 100644
index 17e707effd..0000000000
--- a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Umbraco.Core;
-using Umbraco.Core.DependencyInjection;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Models;
-using Umbraco.Core.Scoping;
-using Umbraco.Core.Services;
-using Umbraco.Infrastructure.PublishedCache;
-using Umbraco.Web.PublishedCache.NuCache.DataSource;
-
-namespace Umbraco.Web.PublishedCache.NuCache
-{
- public class NuCacheComposer : ComponentComposer, IPublishedCacheComposer
- {
- public override void Compose(IUmbracoBuilder builder)
- {
- base.Compose(builder);
-
- // register the NuCache database data source
- builder.Services.AddTransient();
-
- // register the NuCache published snapshot service
- // must register default options, required in the service ctor
- builder.Services.AddTransient(factory => new PublishedSnapshotServiceOptions());
- builder.SetPublishedSnapshotService();
-
- // replace this service since we want to improve the content/media
- // mapping lookups if we are using nucache.
- builder.Services.AddUnique(factory =>
- {
- var idkSvc = new IdKeyMap(factory.GetRequiredService());
- var publishedSnapshotService = factory.GetRequiredService() as PublishedSnapshotService;
- if (publishedSnapshotService != null)
- {
- idkSvc.SetMapper(UmbracoObjectTypes.Document, id => publishedSnapshotService.GetDocumentUid(id), uid => publishedSnapshotService.GetDocumentId(uid));
- idkSvc.SetMapper(UmbracoObjectTypes.Media, id => publishedSnapshotService.GetMediaUid(id), uid => publishedSnapshotService.GetMediaId(uid));
- }
- return idkSvc;
- });
-
- // add the NuCache health check (hidden from type finder)
- // TODO: no NuCache health check yet
- //composition.HealthChecks().Add();
- }
- }
-}
diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs
new file mode 100644
index 0000000000..7bce5e138c
--- /dev/null
+++ b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentRepository.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+using Umbraco.Web.PublishedCache.NuCache;
+
+namespace Umbraco.Infrastructure.PublishedCache.Persistence
+{
+ public interface INuCacheContentRepository
+ {
+ void DeleteContentItem(IContentBase item);
+ IEnumerable GetAllContentSources();
+ IEnumerable GetAllMediaSources();
+ IEnumerable GetBranchContentSources(int id);
+ IEnumerable GetBranchMediaSources(int id);
+ ContentNodeKit GetContentSource(int id);
+ ContentNodeKit GetMediaSource(int id);
+ IEnumerable GetTypeContentSources(IEnumerable ids);
+ IEnumerable GetTypeMediaSources(IEnumerable ids);
+
+ ///
+ /// Refreshes the nucache database row for the
+ ///
+ void RefreshContent(IContent content);
+
+ ///
+ /// Refreshes the nucache database row for the (used for media/members)
+ ///
+ void RefreshEntity(IContentBase content);
+
+ ///
+ /// Rebuilds the caches for content, media and/or members based on the content type ids specified
+ ///
+ /// The operation batch size to process the items
+ /// If not null will process content for the matching content types, if empty will process all content
+ /// If not null will process content for the matching media types, if empty will process all media
+ /// If not null will process content for the matching members types, if empty will process all members
+ void Rebuild(
+ int groupSize = 5000,
+ IReadOnlyCollection contentTypeIds = null,
+ IReadOnlyCollection mediaTypeIds = null,
+ IReadOnlyCollection memberTypeIds = null);
+
+ bool VerifyContentDbCache();
+ bool VerifyMediaDbCache();
+ bool VerifyMemberDbCache();
+ }
+}
diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs
new file mode 100644
index 0000000000..4a3f5b2b5d
--- /dev/null
+++ b/src/Umbraco.PublishedCache.NuCache/Persistence/INuCacheContentService.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using Umbraco.Core.Models;
+using Umbraco.Web.PublishedCache.NuCache;
+
+namespace Umbraco.Infrastructure.PublishedCache.Persistence
+{
+ ///
+ /// Defines a data source for NuCache.
+ ///
+ public interface INuCacheContentService
+ {
+ // TODO: For these required sort orders, would sorting on Path 'just work'?
+ ContentNodeKit GetContentSource(int id);
+
+ ///
+ /// Returns all content ordered by level + sortOrder
+ ///
+ ///
+ /// MUST be ordered by level + parentId + sortOrder!
+ ///
+ IEnumerable GetAllContentSources();
+
+ ///
+ /// Returns branch for content ordered by level + sortOrder
+ ///
+ ///
+ /// MUST be ordered by level + parentId + sortOrder!
+ ///
+ IEnumerable GetBranchContentSources(int id);
+
+ ///
+ /// Returns content by Ids ordered by level + sortOrder
+ ///
+ ///
+ /// MUST be ordered by level + parentId + sortOrder!
+ ///
+ IEnumerable GetTypeContentSources(IEnumerable ids);
+
+ ContentNodeKit GetMediaSource(int id);
+
+ ///
+ /// Returns all media ordered by level + sortOrder
+ ///
+ ///
+ /// MUST be ordered by level + parentId + sortOrder!
+ ///
+ IEnumerable GetAllMediaSources();
+
+ ///
+ /// Returns branch for media ordered by level + sortOrder
+ ///
+ ///
+ /// MUST be ordered by level + parentId + sortOrder!
+ ///
+ IEnumerable GetBranchMediaSources(int id); // must order by level, sortOrder
+
+ ///
+ /// Returns media by Ids ordered by level + sortOrder
+ ///
+ ///
+ /// MUST be ordered by level + parentId + sortOrder!
+ ///
+ IEnumerable GetTypeMediaSources(IEnumerable ids);
+
+ void DeleteContentItem(IContentBase item);
+
+ ///
+ /// Refreshes the nucache database row for the
+ ///
+ void RefreshContent(IContent content);
+
+ ///
+ /// Refreshes the nucache database row for the (used for media/members)
+ ///
+ void RefreshEntity(IContentBase content);
+
+ ///
+ /// Rebuilds the database caches for content, media and/or members based on the content type ids specified
+ ///
+ /// The operation batch size to process the items
+ /// If not null will process content for the matching content types, if empty will process all content
+ /// If not null will process content for the matching media types, if empty will process all media
+ /// If not null will process content for the matching members types, if empty will process all members
+ void Rebuild(
+ int groupSize = 5000,
+ IReadOnlyCollection contentTypeIds = null,
+ IReadOnlyCollection mediaTypeIds = null,
+ IReadOnlyCollection memberTypeIds = null);
+
+ bool VerifyContentDbCache();
+
+ bool VerifyMediaDbCache();
+
+ bool VerifyMemberDbCache();
+ }
+}
diff --git a/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
new file mode 100644
index 0000000000..60370e9be8
--- /dev/null
+++ b/src/Umbraco.PublishedCache.NuCache/Persistence/NuCacheContentRepository.cs
@@ -0,0 +1,735 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using NPoco;
+using Umbraco.Core;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Models;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.Dtos;
+using Umbraco.Core.Persistence.Querying;
+using Umbraco.Core.Persistence.Repositories;
+using Umbraco.Core.Persistence.Repositories.Implement;
+using Umbraco.Core.Scoping;
+using Umbraco.Core.Serialization;
+using Umbraco.Core.Services;
+using Umbraco.Core.Strings;
+using Umbraco.Web.PublishedCache.NuCache;
+using Umbraco.Web.PublishedCache.NuCache.DataSource;
+using static Umbraco.Core.Persistence.SqlExtensionsStatics;
+
+namespace Umbraco.Infrastructure.PublishedCache.Persistence
+{
+ public class NuCacheContentRepository : RepositoryBase, INuCacheContentRepository
+ {
+ private const int PageSize = 500;
+ private readonly ILogger _logger;
+ private readonly IMemberRepository _memberRepository;
+ private readonly IDocumentRepository _documentRepository;
+ private readonly IMediaRepository _mediaRepository;
+ private readonly IShortStringHelper _shortStringHelper;
+ private readonly UrlSegmentProviderCollection _urlSegmentProviders;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NuCacheContentRepository(
+ IScopeAccessor scopeAccessor,
+ AppCaches appCaches,
+ ILogger logger,
+ IMemberRepository memberRepository,
+ IDocumentRepository documentRepository,
+ IMediaRepository mediaRepository,
+ IShortStringHelper shortStringHelper,
+ UrlSegmentProviderCollection urlSegmentProviders)
+ : base(scopeAccessor, appCaches)
+ {
+ _logger = logger;
+ _memberRepository = memberRepository;
+ _documentRepository = documentRepository;
+ _mediaRepository = mediaRepository;
+ _shortStringHelper = shortStringHelper;
+ _urlSegmentProviders = urlSegmentProviders;
+ }
+
+ public void DeleteContentItem(IContentBase item)
+ => Database.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id", new { id = item.Id });
+
+ public void RefreshContent(IContent content)
+ {
+ // always refresh the edited data
+ OnRepositoryRefreshed(content, false);
+
+ if (content.PublishedState == PublishedState.Unpublishing)
+ {
+ // if unpublishing, remove published data from table
+ Database.Execute("DELETE FROM cmsContentNu WHERE nodeId=@id AND published=1", new { id = content.Id });
+ }
+ else if (content.PublishedState == PublishedState.Publishing)
+ {
+ // if publishing, refresh the published data
+ OnRepositoryRefreshed(content, true);
+ }
+ }
+
+ public void RefreshEntity(IContentBase content)
+ => OnRepositoryRefreshed(content, false);
+
+ private void OnRepositoryRefreshed(IContentBase content, bool published)
+ {
+ // use a custom SQL to update row version on each update
+ // db.InsertOrUpdate(dto);
+ ContentNuDto dto = GetDto(content, published);
+
+ Database.InsertOrUpdate(
+ dto,
+ "SET data=@data, rv=rv+1 WHERE nodeId=@id AND published=@published",
+ new
+ {
+ data = dto.Data,
+ id = dto.NodeId,
+ published = dto.Published
+ });
+ }
+
+ public void Rebuild(
+ int groupSize = 5000,
+ IReadOnlyCollection contentTypeIds = null,
+ IReadOnlyCollection mediaTypeIds = null,
+ IReadOnlyCollection memberTypeIds = null)
+ {
+ if (contentTypeIds != null)
+ {
+ RebuildContentDbCache(groupSize, contentTypeIds);
+ }
+
+ if (mediaTypeIds != null)
+ {
+ RebuildContentDbCache(groupSize, mediaTypeIds);
+ }
+
+ if (memberTypeIds != null)
+ {
+ RebuildContentDbCache(groupSize, memberTypeIds);
+ }
+ }
+
+ // assumes content tree lock
+ private void RebuildContentDbCache(int groupSize, IReadOnlyCollection contentTypeIds)
+ {
+ Guid contentObjectType = Constants.ObjectTypes.Document;
+
+ // remove all - if anything fails the transaction will rollback
+ if (contentTypeIds == null || contentTypeIds.Count == 0)
+ {
+ // must support SQL-CE
+ Database.Execute(
+ @"DELETE FROM cmsContentNu
+WHERE cmsContentNu.nodeId IN (
+ SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType
+)",
+ new { objType = contentObjectType });
+ }
+ else
+ {
+ // assume number of ctypes won't blow IN(...)
+ // must support SQL-CE
+ Database.Execute(
+ $@"DELETE FROM cmsContentNu
+WHERE cmsContentNu.nodeId IN (
+ SELECT id FROM umbracoNode
+ JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id
+ WHERE umbracoNode.nodeObjectType=@objType
+ AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)
+)",
+ new { objType = contentObjectType, ctypes = contentTypeIds });
+ }
+
+ // insert back - if anything fails the transaction will rollback
+ IQuery query = SqlContext.Query();
+ if (contentTypeIds != null && contentTypeIds.Count > 0)
+ {
+ query = query.WhereIn(x => x.ContentTypeId, contentTypeIds); // assume number of ctypes won't blow IN(...)
+ }
+
+ long pageIndex = 0;
+ long processed = 0;
+ long total;
+ do
+ {
+ // the tree is locked, counting and comparing to total is safe
+ IEnumerable descendants = _documentRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
+ var items = new List();
+ var count = 0;
+ foreach (IContent c in descendants)
+ {
+ // always the edited version
+ items.Add(GetDto(c, false));
+
+ // and also the published version if it makes any sense
+ if (c.Published)
+ {
+ items.Add(GetDto(c, true));
+ }
+
+ count++;
+ }
+
+ Database.BulkInsertRecords(items);
+ processed += count;
+ } while (processed < total);
+ }
+
+ // assumes media tree lock
+ private void RebuildMediaDbCache(int groupSize, IReadOnlyCollection contentTypeIds)
+ {
+ var mediaObjectType = Constants.ObjectTypes.Media;
+
+ // remove all - if anything fails the transaction will rollback
+ if (contentTypeIds == null || contentTypeIds.Count == 0)
+ {
+ // must support SQL-CE
+ Database.Execute(
+ @"DELETE FROM cmsContentNu
+WHERE cmsContentNu.nodeId IN (
+ SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType
+)",
+ new { objType = mediaObjectType });
+ }
+ else
+ {
+ // assume number of ctypes won't blow IN(...)
+ // must support SQL-CE
+ Database.Execute(
+ $@"DELETE FROM cmsContentNu
+WHERE cmsContentNu.nodeId IN (
+ SELECT id FROM umbracoNode
+ JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id
+ WHERE umbracoNode.nodeObjectType=@objType
+ AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)
+)",
+ new { objType = mediaObjectType, ctypes = contentTypeIds });
+ }
+
+ // insert back - if anything fails the transaction will rollback
+ var query = SqlContext.Query();
+ if (contentTypeIds != null && contentTypeIds.Count > 0)
+ {
+ query = query.WhereIn(x => x.ContentTypeId, contentTypeIds); // assume number of ctypes won't blow IN(...)
+ }
+
+ long pageIndex = 0;
+ long processed = 0;
+ long total;
+ do
+ {
+ // the tree is locked, counting and comparing to total is safe
+ var descendants = _mediaRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
+ var items = descendants.Select(m => GetDto(m, false)).ToList();
+ Database.BulkInsertRecords(items);
+ processed += items.Count;
+ } while (processed < total);
+ }
+
+ // assumes member tree lock
+ private void RebuildMemberDbCache(int groupSize, IReadOnlyCollection contentTypeIds)
+ {
+ Guid memberObjectType = Constants.ObjectTypes.Member;
+
+ // remove all - if anything fails the transaction will rollback
+ if (contentTypeIds == null || contentTypeIds.Count == 0)
+ {
+ // must support SQL-CE
+ Database.Execute(
+ @"DELETE FROM cmsContentNu
+WHERE cmsContentNu.nodeId IN (
+ SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType=@objType
+)",
+ new { objType = memberObjectType });
+ }
+ else
+ {
+ // assume number of ctypes won't blow IN(...)
+ // must support SQL-CE
+ Database.Execute(
+ $@"DELETE FROM cmsContentNu
+WHERE cmsContentNu.nodeId IN (
+ SELECT id FROM umbracoNode
+ JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id
+ WHERE umbracoNode.nodeObjectType=@objType
+ AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes)
+)",
+ new { objType = memberObjectType, ctypes = contentTypeIds });
+ }
+
+ // insert back - if anything fails the transaction will rollback
+ IQuery query = SqlContext.Query();
+ if (contentTypeIds != null && contentTypeIds.Count > 0)
+ {
+ query = query.WhereIn(x => x.ContentTypeId, contentTypeIds); // assume number of ctypes won't blow IN(...)
+ }
+
+ long pageIndex = 0;
+ long processed = 0;
+ long total;
+ do
+ {
+ IEnumerable descendants = _memberRepository.GetPage(query, pageIndex++, groupSize, out total, null, Ordering.By("Path"));
+ ContentNuDto[] items = descendants.Select(m => GetDto(m, false)).ToArray();
+ Database.BulkInsertRecords(items);
+ processed += items.Length;
+ } while (processed < total);
+ }
+
+ // assumes content tree lock
+ public bool VerifyContentDbCache()
+ {
+ // every document should have a corresponding row for edited properties
+ // and if published, may have a corresponding row for published properties
+ Guid contentObjectType = Constants.ObjectTypes.Document;
+
+ var count = Database.ExecuteScalar(
+ $@"SELECT COUNT(*)
+FROM umbracoNode
+JOIN {Constants.DatabaseSchema.Tables.Document} ON umbracoNode.id={Constants.DatabaseSchema.Tables.Document}.nodeId
+LEFT JOIN cmsContentNu nuEdited ON (umbracoNode.id=nuEdited.nodeId AND nuEdited.published=0)
+LEFT JOIN cmsContentNu nuPublished ON (umbracoNode.id=nuPublished.nodeId AND nuPublished.published=1)
+WHERE umbracoNode.nodeObjectType=@objType
+AND nuEdited.nodeId IS NULL OR ({Constants.DatabaseSchema.Tables.Document}.published=1 AND nuPublished.nodeId IS NULL);",
+ new { objType = contentObjectType });
+
+ return count == 0;
+ }
+
+ // assumes media tree lock
+ public bool VerifyMediaDbCache()
+ {
+ // every media item should have a corresponding row for edited properties
+ Guid mediaObjectType = Constants.ObjectTypes.Media;
+
+ var count = Database.ExecuteScalar(
+ @"SELECT COUNT(*)
+FROM umbracoNode
+LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0)
+WHERE umbracoNode.nodeObjectType=@objType
+AND cmsContentNu.nodeId IS NULL
+", new { objType = mediaObjectType });
+
+ return count == 0;
+ }
+
+ // assumes member tree lock
+ public bool VerifyMemberDbCache()
+ {
+ // every member item should have a corresponding row for edited properties
+ var memberObjectType = Constants.ObjectTypes.Member;
+
+ var count = Database.ExecuteScalar(
+ @"SELECT COUNT(*)
+FROM umbracoNode
+LEFT JOIN cmsContentNu ON (umbracoNode.id=cmsContentNu.nodeId AND cmsContentNu.published=0)
+WHERE umbracoNode.nodeObjectType=@objType
+AND cmsContentNu.nodeId IS NULL
+", new { objType = memberObjectType });
+
+ return count == 0;
+ }
+
+ private ContentNuDto GetDto(IContentBase content, bool published)
+ {
+ // should inject these in ctor
+ // BUT for the time being we decide not to support ConvertDbToXml/String
+ // var propertyEditorResolver = PropertyEditorResolver.Current;
+ // var dataTypeService = ApplicationContext.Current.Services.DataTypeService;
+ var propertyData = new Dictionary();
+ foreach (IProperty prop in content.Properties)
+ {
+ var pdatas = new List();
+ foreach (IPropertyValue pvalue in prop.Values)
+ {
+ // sanitize - properties should be ok but ... never knows
+ if (!prop.PropertyType.SupportsVariation(pvalue.Culture, pvalue.Segment))
+ {
+ continue;
+ }
+
+ // note: at service level, invariant is 'null', but here invariant becomes 'string.Empty'
+ var value = published ? pvalue.PublishedValue : pvalue.EditedValue;
+ if (value != null)
+ {
+ pdatas.Add(new PropertyData { Culture = pvalue.Culture ?? string.Empty, Segment = pvalue.Segment ?? string.Empty, Value = value });
+ }
+ }
+
+ propertyData[prop.Alias] = pdatas.ToArray();
+ }
+
+ var cultureData = new Dictionary();
+
+ // sanitize - names should be ok but ... never knows
+ if (content.ContentType.VariesByCulture())
+ {
+ ContentCultureInfosCollection infos = content is IContent document
+ ? published
+ ? document.PublishCultureInfos
+ : document.CultureInfos
+ : content.CultureInfos;
+
+ // ReSharper disable once UseDeconstruction
+ foreach (ContentCultureInfos cultureInfo in infos)
+ {
+ var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(cultureInfo.Culture);
+ cultureData[cultureInfo.Culture] = new CultureVariation
+ {
+ Name = cultureInfo.Name,
+ UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders, cultureInfo.Culture),
+ Date = content.GetUpdateDate(cultureInfo.Culture) ?? DateTime.MinValue,
+ IsDraft = cultureIsDraft
+ };
+ }
+ }
+
+ // the dictionary that will be serialized
+ var nestedData = new ContentNestedData
+ {
+ PropertyData = propertyData,
+ CultureData = cultureData,
+ UrlSegment = content.GetUrlSegment(_shortStringHelper, _urlSegmentProviders)
+ };
+
+ var dto = new ContentNuDto
+ {
+ NodeId = content.Id,
+ Published = published,
+
+ // note that numeric values (which are Int32) are serialized without their
+ // type (eg "value":1234) and JsonConvert by default deserializes them as Int64
+ Data = JsonConvert.SerializeObject(nestedData)
+ };
+
+ return dto;
+ }
+
+ // we want arrays, we want them all loaded, not an enumerable
+ private Sql ContentSourcesSelect(Func, Sql> joins = null)
+ {
+ var sql = Sql()
+
+ .Select(x => Alias(x.NodeId, "Id"), x => Alias(x.UniqueId, "Uid"),
+ x => Alias(x.Level, "Level"), x => Alias(x.Path, "Path"), x => Alias(x.SortOrder, "SortOrder"), x => Alias(x.ParentId, "ParentId"),
+ x => Alias(x.CreateDate, "CreateDate"), x => Alias(x.UserId, "CreatorId"))
+ .AndSelect(x => Alias(x.ContentTypeId, "ContentTypeId"))
+ .AndSelect(x => Alias(x.Published, "Published"), x => Alias(x.Edited, "Edited"))
+
+ .AndSelect(x => Alias(x.Id, "VersionId"), x => Alias(x.Text, "EditName"), x => Alias(x.VersionDate, "EditVersionDate"), x => Alias(x.UserId, "EditWriterId"))
+ .AndSelect(x => Alias(x.TemplateId, "EditTemplateId"))
+
+ .AndSelect("pcver", x => Alias(x.Id, "PublishedVersionId"), x => Alias(x.Text, "PubName"), x => Alias(x.VersionDate, "PubVersionDate"), x => Alias(x.UserId, "PubWriterId"))
+ .AndSelect("pdver", x => Alias(x.TemplateId, "PubTemplateId"))
+
+ .AndSelect("nuEdit", x => Alias(x.Data, "EditData"))
+ .AndSelect("nuPub", x => Alias(x.Data, "PubData"))
+
+ .From();
+
+ if (joins != null)
+ {
+ sql = joins(sql);
+ }
+
+ sql = sql
+ .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
+ .InnerJoin().On((left, right) => left.NodeId == right.NodeId)
+
+ .InnerJoin().On((left, right) => left.NodeId == right.NodeId && right.Current)
+ .InnerJoin().On((left, right) => left.Id == right.Id)
+
+ .LeftJoin(j =>
+ j.InnerJoin("pdver").On((left, right) => left.Id == right.Id && right.Published, "pcver", "pdver"), "pcver")
+ .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcver")
+
+ .LeftJoin("nuEdit").On((left, right) => left.NodeId == right.NodeId && !right.Published, aliasRight: "nuEdit")
+ .LeftJoin("nuPub").On((left, right) => left.NodeId == right.NodeId && right.Published, aliasRight: "nuPub");
+
+ return sql;
+ }
+
+ public ContentNodeKit GetContentSource(int id)
+ {
+ var sql = ContentSourcesSelect()
+ .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && x.NodeId == id && !x.Trashed)
+ .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
+
+ var dto = Database.Fetch(sql).FirstOrDefault();
+ return dto == null ? new ContentNodeKit() : CreateContentNodeKit(dto);
+ }
+
+ public IEnumerable GetAllContentSources()
+ {
+ var sql = ContentSourcesSelect()
+ .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
+ .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
+
+ // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
+ // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
+
+ foreach (var row in Database.QueryPaged(PageSize, sql))
+ {
+ yield return CreateContentNodeKit(row);
+ }
+ }
+
+ public IEnumerable GetBranchContentSources(int id)
+ {
+ var syntax = SqlSyntax;
+ var sql = ContentSourcesSelect(
+ s => s.InnerJoin("x").On((left, right) => left.NodeId == right.NodeId || SqlText(left.Path, right.Path, (lp, rp) => $"({lp} LIKE {syntax.GetConcat(rp, "',%'")})"), aliasRight: "x"))
+ .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
+ .Where(x => x.NodeId == id, "x")
+ .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
+
+ // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
+ // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
+
+ foreach (var row in Database.QueryPaged(PageSize, sql))
+ {
+ yield return CreateContentNodeKit(row);
+ }
+ }
+
+ public IEnumerable GetTypeContentSources(IEnumerable ids)
+ {
+ if (!ids.Any())
+ yield break;
+
+ var sql = ContentSourcesSelect()
+ .Where(x => x.NodeObjectType == Constants.ObjectTypes.Document && !x.Trashed)
+ .WhereIn(x => x.ContentTypeId, ids)
+ .OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder);
+
+ // We need to page here. We don't want to iterate over every single row in one connection cuz this can cause an SQL Timeout.
+ // We also want to read with a db reader and not load everything into memory, QueryPaged lets us do that.
+
+ foreach (var row in Database.QueryPaged(PageSize, sql))
+ {
+ yield return CreateContentNodeKit(row);
+ }
+ }
+
+ private Sql MediaSourcesSelect(Func, Sql> joins = null)
+ {
+ var sql = Sql()
+
+ .Select