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/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..af8f72ce6d 100644
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
+++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
@@ -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.
@@ -25,6 +23,11 @@ namespace Umbraco.Web.PublishedCache
*
*/
+ ///
+ /// Gets the published snapshot accessor.
+ ///
+ IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
+
///
/// Creates a published snapshot.
///
@@ -35,11 +38,6 @@ namespace Umbraco.Web.PublishedCache
/// which is not specified and depends on the actual published snapshot service implementation.
IPublishedSnapshot CreatePublishedSnapshot(string previewToken);
- ///
- /// Gets the published snapshot accessor.
- ///
- IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
-
///
/// Ensures that the published snapshot has the proper environment to run.
///
@@ -47,74 +45,24 @@ namespace Umbraco.Web.PublishedCache
/// 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 +108,12 @@ namespace Umbraco.Web.PublishedCache
/// The changes.
void Notify(DomainCacheRefresher.JsonPayload[] payloads);
- #endregion
-
- #region Status
-
+ // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus?
string GetStatus();
+ // TODO: This is weird, why is this is this a thing? Maybe IPublishedSnapshotStatus?
string StatusUrl { get; }
- #endregion
-
void Collect();
}
}
diff --git a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
index 9c71bdc04b..f33eb61e8f 100644
--- a/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
+++ b/src/Umbraco.Core/PublishedCache/PublishedSnapshotServiceBase.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.PublishedContent;
@@ -6,46 +6,73 @@ using Umbraco.Web.Cache;
namespace Umbraco.Web.PublishedCache
{
+ // TODO: This base class probably shouldn't exist
public abstract class PublishedSnapshotServiceBase : IPublishedSnapshotService
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
protected PublishedSnapshotServiceBase(IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor)
{
PublishedSnapshotAccessor = publishedSnapshotAccessor;
VariationContextAccessor = variationContextAccessor;
}
+ ///
public IPublishedSnapshotAccessor PublishedSnapshotAccessor { get; }
+
+ ///
+ /// Gets the
+ ///
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 abstract void Rebuild(
+ int groupSize = 5000,
+ IReadOnlyCollection contentTypeIds = null,
+ IReadOnlyCollection mediaTypeIds = null,
+ IReadOnlyCollection memberTypeIds = null);
+ ///
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/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 88%
rename from src/Umbraco.Core/IUmbracoContext.cs
rename to src/Umbraco.Core/Web/IUmbracoContext.cs
index 681dedbfd2..7fa02e3b73 100644
--- a/src/Umbraco.Core/IUmbracoContext.cs
+++ b/src/Umbraco.Core/Web/IUmbracoContext.cs
@@ -49,11 +49,12 @@ namespace Umbraco.Web
///
/// Boolean value indicating whether the current request is a front-end umbraco request
///
- bool IsFrontEndUmbracoRequest { get; }
+ bool IsFrontEndUmbracoRequest { get; } // TODO: This could easily be an ext method and mocking just means setting the published request to null
///
/// 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; }
///
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/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/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs
index 2533eaea8e..a84b34b75a 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;
@@ -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/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..d7f6e63c55 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,101 @@ 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);
+
+ protected abstract Sql GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere
+
+ 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 +222,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/NPocoRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs
index 392e7bdf1f..ff820da577 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/NPocoRepositoryBase.cs
@@ -1,7 +1,7 @@
-using System;
+using System;
using System.Collections.Generic;
-using NPoco;
using Microsoft.Extensions.Logging;
+using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Persistence.Querying;
@@ -13,9 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
///
/// Represent an abstract Repository for NPoco based repositories
///
- ///
- ///
- public abstract class NPocoRepositoryBase : RepositoryBase
+ public abstract class NPocoRepositoryBase : EntityRepositoryBase
where TEntity : class, IEntity
{
///
@@ -24,58 +22,5 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
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/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/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/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index 55fa3457ed..35b7443338 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -43,8 +43,6 @@ namespace Umbraco.Infrastructure.Runtime
_databaseFactory = databaseFactory;
_eventAggregator = eventAggregator;
_hostingEnvironment = hostingEnvironment;
-
-
_logger = _loggerFactory.CreateLogger();
}
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/ModelsBuilderComposer.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
index 81869a9261..344e1b025a 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,6 +11,7 @@ using Umbraco.Core.DependencyInjection;
namespace Umbraco.ModelsBuilder.Embedded.Compose
{
+ // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there
[ComposeBefore(typeof(IPublishedCacheComposer))]
public sealed class ModelsBuilderComposer : ICoreComposer
{
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..e79c195b46 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)
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/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
index 17e707effd..5e618d361c 100644
--- a/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs
+++ b/src/Umbraco.PublishedCache.NuCache/NuCacheComposer.cs
@@ -1,23 +1,26 @@
-using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
-using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
+using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Models;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Infrastructure.PublishedCache;
-using Umbraco.Web.PublishedCache.NuCache.DataSource;
+using Umbraco.Infrastructure.PublishedCache.Persistence;
namespace Umbraco.Web.PublishedCache.NuCache
{
- public class NuCacheComposer : ComponentComposer, IPublishedCacheComposer
+ // TODO: We'll need to change this stuff to IUmbracoBuilder ext and control the order of things there,
+ // see comment in ModelsBuilderComposer which requires this weird IPublishedCacheComposer
+ public class NuCacheComposer : IComposer, IPublishedCacheComposer
{
- public override void Compose(IUmbracoBuilder builder)
+ ///
+ public void Compose(IUmbracoBuilder builder)
{
- base.Compose(builder);
-
// register the NuCache database data source
- builder.Services.AddTransient();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
// register the NuCache published snapshot service
// must register default options, required in the service ctor
@@ -26,21 +29,22 @@ namespace Umbraco.Web.PublishedCache.NuCache
// 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());
- var publishedSnapshotService = factory.GetRequiredService() as PublishedSnapshotService;
- if (publishedSnapshotService != null)
+ 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();
+ // 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