diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml
deleted file mode 100644
index 5d53b2f12e..0000000000
--- a/.github/ISSUE_TEMPLATE/02_feature_request.yml
+++ /dev/null
@@ -1,33 +0,0 @@
----
-name: 📮 Feature Request
-description: Open a feature request, if you want to propose a new feature.
-labels: type/feature
-body:
-- type: dropdown
- id: version
- attributes:
- label: Umbraco version
- description: Which major Umbraco version are you proposing a feature for?
- options:
- - v8
- - v9
- validations:
- required: true
-- type: textarea
- id: summary
- attributes:
- label: Description
- description: Write a brief desciption of your proposed new feature.
- validations:
- required: true
-- type: textarea
- attributes:
- label: How can you help?
- id: help
- description: Umbraco''s core team has limited available time, but maybe you can help?
- placeholder: >
- If we can not work on your suggestion, please don't take it personally. Most likely, it's either:
-
- - We think your idea is valid, but we can't find the time to work on it.
-
- - Your idea might be better suited as a package, if it's not suitable for the majority of users.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index d5418ad270..ecf10b8854 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
+ - name: 💡 Features and ideas
+ url: https://github.com/umbraco/Umbraco-CMS/discussions/new?category=features-and-ideas
+ about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans
- name: ⁉️ Support Question
url: https://our.umbraco.com
about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum.
diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs
new file mode 100644
index 0000000000..ed5e6a5d26
--- /dev/null
+++ b/src/Umbraco.Core/Configuration/Models/NuCacheSerializerType.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Umbraco.
+// See LICENSE for more details.
+
+namespace Umbraco.Cms.Core.Configuration.Models
+{
+ ///
+ /// The serializer type that nucache uses to persist documents in the database.
+ ///
+ public enum NuCacheSerializerType
+ {
+ JSON,
+
+ MessagePack
+ }
+}
diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs
index aa67038702..865d50afbc 100644
--- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs
@@ -12,5 +12,15 @@ namespace Umbraco.Cms.Core.Configuration.Models
/// Gets or sets a value defining the BTree block size.
///
public int? BTreeBlockSize { get; set; }
+
+ ///
+ /// The serializer type that nucache uses to persist documents in the database.
+ ///
+ public NuCacheSerializerType NuCacheSerializerType { get; set; }
+
+ ///
+ /// The paging size to use for nucache SQL queries.
+ ///
+ public int SqlPageSize { get; set; } = 1000;
}
}
diff --git a/src/Umbraco.Core/Constants-SqlTemplates.cs b/src/Umbraco.Core/Constants-SqlTemplates.cs
index 116f04925e..c6bee5ca4f 100644
--- a/src/Umbraco.Core/Constants-SqlTemplates.cs
+++ b/src/Umbraco.Core/Constants-SqlTemplates.cs
@@ -1,4 +1,4 @@
-namespace Umbraco.Cms.Core
+namespace Umbraco.Cms.Core
{
public static partial class Constants
{
@@ -24,6 +24,20 @@
{
public const string EnsureUniqueNodeName = "Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName";
}
+
+ public static class NuCacheDatabaseDataSource
+ {
+ public const string WhereNodeId = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeId";
+ public const string WhereNodeIdX = "Umbraco.Web.PublishedCache.NuCache.DataSource.WhereNodeIdX";
+ public const string SourcesSelectUmbracoNodeJoin = "Umbraco.Web.PublishedCache.NuCache.DataSource.SourcesSelectUmbracoNodeJoin";
+ public const string ContentSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesSelect";
+ public const string ContentSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.ContentSourcesCount";
+ public const string MediaSourcesSelect = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesSelect";
+ public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount";
+ public const string ObjectTypeNotTrashedFilter = "Umbraco.Web.PublishedCache.NuCache.DataSource.ObjectTypeNotTrashedFilter";
+ public const string OrderByLevelIdSortOrder = "Umbraco.Web.PublishedCache.NuCache.DataSource.OrderByLevelIdSortOrder";
+
+ }
}
}
}
diff --git a/src/Umbraco.Core/Models/IContentBase.cs b/src/Umbraco.Core/Models/IContentBase.cs
index 4900ab00e1..1aac7dd1a5 100644
--- a/src/Umbraco.Core/Models/IContentBase.cs
+++ b/src/Umbraco.Core/Models/IContentBase.cs
@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Models.Entities;
namespace Umbraco.Cms.Core.Models
{
+
///
/// Provides a base class for content items.
///
diff --git a/src/Umbraco.Core/Models/IReadOnlyContentBase.cs b/src/Umbraco.Core/Models/IReadOnlyContentBase.cs
new file mode 100644
index 0000000000..e327a49f29
--- /dev/null
+++ b/src/Umbraco.Core/Models/IReadOnlyContentBase.cs
@@ -0,0 +1,72 @@
+using System;
+
+namespace Umbraco.Cms.Core.Models
+{
+ public interface IReadOnlyContentBase
+ {
+ ///
+ /// Gets the integer identifier of the entity.
+ ///
+ int Id { get; }
+
+ ///
+ /// Gets the Guid unique identifier of the entity.
+ ///
+ Guid Key { get; }
+
+ ///
+ /// Gets the creation date.
+ ///
+ DateTime CreateDate { get; }
+
+ ///
+ /// Gets the last update date.
+ ///
+ DateTime UpdateDate { get; }
+
+ ///
+ /// Gets the name of the entity.
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the identifier of the user who created this entity.
+ ///
+ int CreatorId { get; }
+
+ ///
+ /// Gets the identifier of the parent entity.
+ ///
+ int ParentId { get; }
+
+ ///
+ /// Gets the level of the entity.
+ ///
+ int Level { get; }
+
+ ///
+ /// Gets the path to the entity.
+ ///
+ string Path { get; }
+
+ ///
+ /// Gets the sort order of the entity.
+ ///
+ int SortOrder { get; }
+
+ ///
+ /// Gets the content type id
+ ///
+ int ContentTypeId { get; }
+
+ ///
+ /// Gets the identifier of the writer.
+ ///
+ int WriterId { get; }
+
+ ///
+ /// Gets the version identifier.
+ ///
+ int VersionId { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs
new file mode 100644
index 0000000000..77d285596a
--- /dev/null
+++ b/src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace Umbraco.Cms.Core.Models
+{
+ public struct ReadOnlyContentBaseAdapter : IReadOnlyContentBase
+ {
+ private readonly IContentBase _content;
+
+ private ReadOnlyContentBaseAdapter(IContentBase content)
+ {
+ _content = content ?? throw new ArgumentNullException(nameof(content));
+ }
+
+ public static ReadOnlyContentBaseAdapter Create(IContentBase content) => new ReadOnlyContentBaseAdapter(content);
+
+ public int Id => _content.Id;
+
+ public Guid Key => _content.Key;
+
+ public DateTime CreateDate => _content.CreateDate;
+
+ public DateTime UpdateDate => _content.UpdateDate;
+
+ public string Name => _content.Name;
+
+ public int CreatorId => _content.CreatorId;
+
+ public int ParentId => _content.ParentId;
+
+ public int Level => _content.Level;
+
+ public string Path => _content.Path;
+
+ public int SortOrder => _content.SortOrder;
+
+ public int ContentTypeId => _content.ContentTypeId;
+
+ public int WriterId => _content.WriterId;
+
+ public int VersionId => _content.VersionId;
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs
index f8fbc051cd..639dad1c23 100644
--- a/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs
+++ b/src/Umbraco.Core/PropertyEditors/DataEditorAttribute.cs
@@ -2,6 +2,7 @@
namespace Umbraco.Cms.Core.PropertyEditors
{
+
///
/// Marks a class that represents a data editor.
///
diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs
new file mode 100644
index 0000000000..817bc5aeae
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs
@@ -0,0 +1,15 @@
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.PropertyEditors
+{
+ ///
+ /// Determines if a property type's value should be compressed in memory
+ ///
+ ///
+ ///
+ ///
+ public interface IPropertyCacheCompression
+ {
+ bool IsCompressed(IReadOnlyContentBase content, string propertyTypeAlias);
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs
new file mode 100644
index 0000000000..86bda9e799
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs
@@ -0,0 +1,9 @@
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.PropertyEditors
+{
+ public interface IPropertyCacheCompressionOptions
+ {
+ bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor);
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs
new file mode 100644
index 0000000000..f2020ecbca
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs
@@ -0,0 +1,12 @@
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.PropertyEditors
+{
+ ///
+ /// Default implementation for which does not compress any property data
+ ///
+ public sealed class NoopPropertyCacheCompressionOptions : IPropertyCacheCompressionOptions
+ {
+ public bool IsCompressed(IReadOnlyContentBase content, IPropertyType propertyType, IDataEditor dataEditor) => false;
+ }
+}
diff --git a/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs
new file mode 100644
index 0000000000..3664be6101
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs
@@ -0,0 +1,51 @@
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Core.PropertyEditors
+{
+
+ ///
+ /// Compresses property data based on config
+ ///
+ public class PropertyCacheCompression : IPropertyCacheCompression
+ {
+ private readonly IPropertyCacheCompressionOptions _compressionOptions;
+ private readonly IReadOnlyDictionary _contentTypes;
+ private readonly PropertyEditorCollection _propertyEditors;
+ private readonly ConcurrentDictionary<(int contentTypeId, string propertyAlias), bool> _isCompressedCache;
+
+ public PropertyCacheCompression(
+ IPropertyCacheCompressionOptions compressionOptions,
+ IReadOnlyDictionary contentTypes,
+ PropertyEditorCollection propertyEditors,
+ ConcurrentDictionary<(int, string), bool> compressedStoragePropertyEditorCache)
+ {
+ _compressionOptions = compressionOptions;
+ _contentTypes = contentTypes ?? throw new System.ArgumentNullException(nameof(contentTypes));
+ _propertyEditors = propertyEditors ?? throw new System.ArgumentNullException(nameof(propertyEditors));
+ _isCompressedCache = compressedStoragePropertyEditorCache;
+ }
+
+ public bool IsCompressed(IReadOnlyContentBase content, string alias)
+ {
+ var compressedStorage = _isCompressedCache.GetOrAdd((content.ContentTypeId, alias), x =>
+ {
+ if (!_contentTypes.TryGetValue(x.contentTypeId, out var ct))
+ return false;
+
+ var propertyType = ct.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == alias);
+ if (propertyType == null)
+ return false;
+
+ if (!_propertyEditors.TryGet(propertyType.PropertyEditorAlias, out var propertyEditor))
+ return false;
+
+ return _compressionOptions.IsCompressed(content, propertyType, propertyEditor);
+ });
+
+ return compressedStorage;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs
index 7358611711..5fa8c16832 100644
--- a/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs
+++ b/src/Umbraco.Core/PublishedCache/IPublishedContentCache.cs
@@ -36,16 +36,26 @@ namespace Umbraco.Cms.Core.PublishedCache
///
/// A value indicating whether to consider unpublished content.
/// The content unique identifier.
- /// The route.
- /// The value of overrides defaults.
+ /// A special string formatted route path.
+ ///
+ ///
+ /// The resulting string is a special encoded route string that may contain the domain ID
+ /// for the current route. If a domain is present the string will be prefixed with the domain ID integer, example: {domainId}/route-path-of-item
+ ///
+ /// The value of overrides defaults.
+ ///
string GetRouteById(bool preview, int contentId, string culture = null);
///
/// Gets the route for a content identified by its unique identifier.
///
/// The content unique identifier.
- /// The route.
+ /// A special string formatted route path.
/// Considers published or unpublished content depending on defaults.
+ ///
+ /// The resulting string is a special encoded route string that may contain the domain ID
+ /// for the current route. If a domain is present the string will be prefixed with the domain ID integer, example: {domainId}/route-path-of-item
+ ///
string GetRouteById(int contentId, string culture = null);
}
}
diff --git a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
index d1e113d16c..360f277da3 100644
--- a/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
+++ b/src/Umbraco.Core/PublishedCache/IPublishedSnapshotService.cs
@@ -36,7 +36,6 @@ namespace Umbraco.Cms.Core.PublishedCache
///
/// 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
@@ -47,7 +46,6 @@ namespace Umbraco.Cms.Core.PublishedCache
/// RefreshAllPublishedSnapshot method.
///
void Rebuild(
- int groupSize = 5000,
IReadOnlyCollection contentTypeIds = null,
IReadOnlyCollection mediaTypeIds = null,
IReadOnlyCollection memberTypeIds = null);
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
index 9becde2ebe..34a14d7deb 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs
@@ -1,4 +1,5 @@
using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
namespace Umbraco.Cms.Infrastructure.DependencyInjection
@@ -14,5 +15,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
/// The builder.
public static MapperCollectionBuilder Mappers(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder();
+
+ public static NPocoMapperCollectionBuilder NPocoMappers(this IUmbracoBuilder builder)
+ => builder.WithCollectionBuilder();
}
}
diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
index f49931c105..e974c633a0 100644
--- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
+++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs
@@ -45,6 +45,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Install;
using Umbraco.Cms.Infrastructure.Migrations.PostMigrations;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_0_0.DataTypes;
using Umbraco.Cms.Infrastructure.Persistence;
+using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Infrastructure.Runtime;
using Umbraco.Cms.Infrastructure.Search;
using Umbraco.Cms.Infrastructure.Serialization;
@@ -66,6 +67,8 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
builder.Services.AddUnique();
builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase());
builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext);
+ builder.NPocoMappers().Add();
+
builder.Services.AddUnique();
builder.Services.AddUnique();
@@ -206,9 +209,18 @@ namespace Umbraco.Cms.Infrastructure.DependencyInjection
var databaseSchemaCreatorFactory = factory.GetRequiredService();
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var loggerFactory = factory.GetRequiredService();
+ var npocoMappers = factory.GetRequiredService();
return globalSettings.Value.MainDomLock.Equals("SqlMainDomLock") || isWindows == false
- ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment, databaseSchemaCreatorFactory)
+ ? (IMainDomLock)new SqlMainDomLock(
+ loggerFactory.CreateLogger(),
+ loggerFactory,
+ globalSettings,
+ connectionStrings,
+ dbCreator,
+ hostingEnvironment,
+ databaseSchemaCreatorFactory,
+ npocoMappers)
: new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment);
});
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
index 12d8a4afd2..56555fe006 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs
@@ -9,6 +9,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_10_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_6_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_7_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_9_0;
+using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_9_0_0;
using Umbraco.Extensions;
@@ -200,6 +201,10 @@ namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade
// to 8.10.0
To("{D6A8D863-38EC-44FB-91EC-ACD6A668BD18}");
+ // to 8.15.0...
+ To("{8DDDCD0B-D7D5-4C97-BD6A-6B38CA65752F}");
+ To("{4695D0C9-0729-4976-985B-048D503665D8}");
+
// to 9.0.0
To("{22D801BA-A1FF-4539-BFCC-2139B55594F8}");
To("{50A43237-A6F4-49E2-A7A6-5DAD65C84669}");
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_15_0/AddCmsContentNuByteColumn.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_15_0/AddCmsContentNuByteColumn.cs
new file mode 100644
index 0000000000..d73e617e45
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_15_0/AddCmsContentNuByteColumn.cs
@@ -0,0 +1,21 @@
+using System.Linq;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
+{
+ public class AddCmsContentNuByteColumn : MigrationBase
+ {
+ public AddCmsContentNuByteColumn(IMigrationContext context)
+ : base(context)
+ {
+
+ }
+
+ public override void Migrate()
+ {
+ var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList();
+
+ AddColumnIfNotExists(columns, "dataRaw");
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_15_0/UpgradedIncludeIndexes.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_15_0/UpgradedIncludeIndexes.cs
new file mode 100644
index 0000000000..e7989962b8
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_8_15_0/UpgradedIncludeIndexes.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Infrastructure.Migrations.Expressions.Execute.Expressions;
+using Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions;
+using Umbraco.Cms.Infrastructure.Persistence.Dtos;
+
+namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_8_15_0
+{
+ public class UpgradedIncludeIndexes : MigrationBase
+ {
+ public UpgradedIncludeIndexes(IMigrationContext context)
+ : base(context)
+ {
+
+ }
+
+ public override void Migrate()
+ {
+ // Need to drop the FK for the redirect table before modifying the unique id index
+ Delete.ForeignKey()
+ .FromTable(Constants.DatabaseSchema.Tables.RedirectUrl)
+ .ForeignColumn("contentKey")
+ .ToTable(NodeDto.TableName)
+ .PrimaryColumn("uniqueID")
+ .Do();
+ var nodeDtoIndexes = new[] { $"IX_{NodeDto.TableName}_UniqueId", $"IX_{NodeDto.TableName}_ObjectType", $"IX_{NodeDto.TableName}_Level" };
+ DeleteIndexes(nodeDtoIndexes); // delete existing ones
+ CreateIndexes(nodeDtoIndexes); // update/add
+ // Now re-create the FK for the redirect table
+ Create.ForeignKey()
+ .FromTable(Constants.DatabaseSchema.Tables.RedirectUrl)
+ .ForeignColumn("contentKey")
+ .ToTable(NodeDto.TableName)
+ .PrimaryColumn("uniqueID")
+ .Do();
+
+
+ var contentVersionIndexes = new[] { $"IX_{ContentVersionDto.TableName}_NodeId", $"IX_{ContentVersionDto.TableName}_Current" };
+ DeleteIndexes(contentVersionIndexes); // delete existing ones
+ CreateIndexes(contentVersionIndexes); // update/add
+ }
+
+ private void DeleteIndexes(params string[] toDelete)
+ {
+ var tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax);
+
+ foreach (var i in toDelete)
+ if (IndexExists(i))
+ Delete.Index(i).OnTable(tableDef.Name).Do();
+
+ }
+
+ private void CreateIndexes(params string[] toCreate)
+ {
+ var tableDef = DefinitionFactory.GetTableDefinition(typeof(T), Context.SqlContext.SqlSyntax);
+
+ foreach (var c in toCreate)
+ {
+ // get the definition by name
+ var index = tableDef.Indexes.First(x => x.Name == c);
+ new ExecuteSqlStatementExpression(Context) { SqlStatement = Context.SqlContext.SqlSyntax.Format(index) }.Execute();
+ }
+
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs b/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs
index e8d7e89522..cc80a34310 100644
--- a/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs
+++ b/src/Umbraco.Infrastructure/Models/MediaWithCrops.cs
@@ -3,7 +3,7 @@
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
-namespace Umbraco.Core.Models
+namespace Umbraco.Cms.Core.Models
{
///
/// Model used in Razor Views for rendering
diff --git a/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs b/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs
index 8758d17d07..e7286d683f 100644
--- a/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs
+++ b/src/Umbraco.Infrastructure/Models/PathValidationExtensions.cs
@@ -1,7 +1,6 @@
-using System;
+using System;
using System.IO;
using Microsoft.Extensions.Logging;
-using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Extensions;
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/IndexAttribute.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/IndexAttribute.cs
index 5c9f41b097..6d1db2dc5f 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/IndexAttribute.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseAnnotations/IndexAttribute.cs
@@ -31,5 +31,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations
/// Gets or sets the column name(s) for the current index
///
public string ForColumns { get; set; }
+
+ ///
+ /// Gets or sets the column name(s) for the columns to include in the index
+ ///
+ public string IncludeColumns { get; set; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
index a3ca285918..407672c995 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/DefinitionFactory.cs
@@ -167,6 +167,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
definition.Columns.Add(new IndexColumnDefinition {Name = column, Direction = Direction.Ascending});
}
}
+ if (string.IsNullOrEmpty(attribute.IncludeColumns) == false)
+ {
+ var columns = attribute.IncludeColumns.Split(',').Select(p => p.Trim());
+ foreach (var column in columns)
+ {
+ definition.IncludeColumns.Add(new IndexColumnDefinition { Name = column, Direction = Direction.Ascending });
+ }
+ }
return definition;
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/IndexDefinition.cs b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/IndexDefinition.cs
index a1e14ac580..822bf79383 100644
--- a/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/IndexDefinition.cs
+++ b/src/Umbraco.Infrastructure/Persistence/DatabaseModelDefinitions/IndexDefinition.cs
@@ -5,17 +5,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.DatabaseModelDefinitions
{
public class IndexDefinition
{
- public IndexDefinition()
- {
- Columns = new List();
- }
-
public virtual string Name { get; set; }
public virtual string SchemaName { get; set; }
public virtual string TableName { get; set; }
public virtual string ColumnName { get; set; }
- public virtual ICollection Columns { get; set; }
+ public virtual ICollection Columns { get; set; } = new List();
+ public virtual ICollection IncludeColumns { get; set; } = new List();
public IndexTypes IndexType { get; set; }
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs
index 27f277293c..a5ca3496ee 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentNuDto.cs
@@ -25,9 +25,16 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
///
[Column("data")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
+ [NullSetting(NullSetting = NullSettings.Null)]
public string Data { get; set; }
[Column("rv")]
public long Rv { get; set; }
+
+ [Column("dataRaw")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ public byte[] RawData { get; set; }
+
+
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs
index 72e4edf85d..53e90859d9 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ContentVersionDto.cs
@@ -19,7 +19,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
[Column("nodeId")]
[ForeignKey(typeof(ContentDto))]
- [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current")]
+ [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current", IncludeColumns = "id,versionDate,text,userId")]
public int NodeId { get; set; }
[Column("versionDate")] // TODO: db rename to 'updateDate'
@@ -32,6 +32,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
public int? UserId { get => _userId == 0 ? null : _userId; set => _userId = value; } //return null if zero
[Column("current")]
+ [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Current", IncludeColumns = "nodeId")]
public bool Current { get; set; }
// about current:
diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/NodeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/NodeDto.cs
index d401a6f5b8..4639e4529a 100644
--- a/src/Umbraco.Infrastructure/Persistence/Dtos/NodeDto.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Dtos/NodeDto.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
[Column("uniqueId")]
[NullSetting(NullSetting = NullSettings.NotNull)]
- [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId")]
+ [Index(IndexTypes.UniqueNonClustered, Name = "IX_" + TableName + "_UniqueId", IncludeColumns = "parentId,level,path,sortOrder,trashed,nodeUser,text,createDate")]
[Constraint(Default = SystemMethods.NewGuid)]
public Guid UniqueId { get; set; }
@@ -29,7 +29,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ParentId")]
public int ParentId { get; set; }
+ // NOTE: This index is primarily for the nucache data lookup, see https://github.com/umbraco/Umbraco-CMS/pull/8365#issuecomment-673404177
[Column("level")]
+ [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Level", ForColumns = "level,parentId,sortOrder,nodeObjectType,trashed", IncludeColumns = "nodeUser,path,uniqueId,createDate")]
public short Level { get; set; }
[Column("path")]
@@ -55,8 +57,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos
public string Text { get; set; }
[Column("nodeObjectType")] // TODO: db rename to 'objectType'
- [NullSetting(NullSetting = NullSettings.Null)]
- [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType")]
+ [NullSetting(NullSetting = NullSettings.Null)]
+ [Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType", ForColumns = "nodeObjectType,trashed", IncludeColumns = "uniqueId,parentId,level,path,sortOrder,nodeUser,text,createDate")]
public Guid? NodeObjectType { get; set; }
[Column("createDate")]
diff --git a/src/Umbraco.Infrastructure/Persistence/Mappers/PocoMapper.cs b/src/Umbraco.Infrastructure/Persistence/Mappers/NullableDateMapper.cs
similarity index 89%
rename from src/Umbraco.Infrastructure/Persistence/Mappers/PocoMapper.cs
rename to src/Umbraco.Infrastructure/Persistence/Mappers/NullableDateMapper.cs
index 835451755b..c647c4b93e 100644
--- a/src/Umbraco.Infrastructure/Persistence/Mappers/PocoMapper.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Mappers/NullableDateMapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Reflection;
using NPoco;
@@ -7,7 +7,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
///
/// Extends NPoco default mapper and ensures that nullable dates are not saved to the database.
///
- public class PocoMapper : DefaultMapper
+ public class NullableDateMapper : DefaultMapper
{
public override Func