From 05079efdd367b2706ec06cd8b608c6cc5c3765bf Mon Sep 17 00:00:00 2001 From: Marek Dzikiewicz Date: Thu, 29 Jun 2023 13:04:23 -0400 Subject: [PATCH 01/11] Fix mapping records in nested namespaces (#8681) (#8682) --- src/Orchard/Data/MapAsRecordAttribute.cs | 18 ++++++++++++++++++ .../ShellBuilders/CompositionStrategy.cs | 10 ++++++++-- src/Orchard/Orchard.Framework.csproj | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/Orchard/Data/MapAsRecordAttribute.cs diff --git a/src/Orchard/Data/MapAsRecordAttribute.cs b/src/Orchard/Data/MapAsRecordAttribute.cs new file mode 100644 index 000000000..2ef6abecd --- /dev/null +++ b/src/Orchard/Data/MapAsRecordAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Orchard.Data { + /// + /// Marks whether a class should be mapped as an NHibernate record + /// + public class MapAsRecordAttribute : Attribute { + private readonly bool _enabled; + + public MapAsRecordAttribute() : this(true) { } + + public MapAsRecordAttribute(bool enabled) { + _enabled = enabled; + } + + public bool Enabled => _enabled; + } +} \ No newline at end of file diff --git a/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs b/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs index 332d70964..5a4e48480 100644 --- a/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs +++ b/src/Orchard/Environment/ShellBuilders/CompositionStrategy.cs @@ -6,6 +6,7 @@ using System.Web.Mvc; using Autofac.Core; using Orchard.ContentManagement; using Orchard.ContentManagement.Records; +using Orchard.Data; using Orchard.Environment.Configuration; using Orchard.Environment.Descriptor.Models; using Orchard.Environment.Extensions; @@ -96,7 +97,7 @@ namespace Orchard.Environment.ShellBuilders { } var feature = availableFeatures[shellFeature]; - + foreach (var childDependency in ExpandDependenciesInternal(availableFeatures, feature.Dependencies, dependentFeatureDescriptor: feature)) yield return childDependency; @@ -197,7 +198,12 @@ namespace Orchard.Environment.ShellBuilders { } private static bool IsRecord(Type type) { - return ((type.Namespace ?? "").EndsWith(".Models") || (type.Namespace ?? "").EndsWith(".Records")) && + var mapAsRecordAttr = type.GetCustomAttributes(typeof(MapAsRecordAttribute), false) + .OfType() + .FirstOrDefault(); + + return ((type.Namespace ?? "").EndsWith(".Models") || (type.Namespace ?? "").EndsWith(".Records") || mapAsRecordAttr?.Enabled == true) && + mapAsRecordAttr?.Enabled != false && type.GetProperty("Id") != null && (type.GetProperty("Id").GetAccessors()).All(x => x.IsVirtual) && !type.IsSealed && diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 9ad2d40c4..8d6ae0e9f 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -150,6 +150,7 @@ + From 0b001c76c297cc4240d09b78e4f7dc5fac04cf11 Mon Sep 17 00:00:00 2001 From: Andrea Piovanelli <83577153+AndreaPiovanelliLaser@users.noreply.github.com> Date: Fri, 30 Jun 2023 08:49:20 +0200 Subject: [PATCH 02/11] Added flag to display a single model error for MediaLibraryPickerField (#8700) * Added flag to display model error by LocalizationExtensionHandler if media have been removed from the field only. This ensures that no duplicate model error has displayed. * Restored formatting for else branches --------- Co-authored-by: Matteo Piovanelli --- ...PickerFieldLocalizationExtensionHandler.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Handlers/MediaLibraryPickerFieldLocalizationExtensionHandler.cs b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Handlers/MediaLibraryPickerFieldLocalizationExtensionHandler.cs index 3494ab2ad..ed73b9b49 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary/Handlers/MediaLibraryPickerFieldLocalizationExtensionHandler.cs +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary/Handlers/MediaLibraryPickerFieldLocalizationExtensionHandler.cs @@ -45,6 +45,12 @@ namespace Orchard.MediaLibrary.Handlers { //try to replace items in the field with their translation var itemsInField = _contentManager.GetMany(field.Ids, VersionOptions.Latest, QueryHints.Empty); var mediaIds = new List(); + + // Flag to check if media have been removed from the field. + // This happens when the field is set to remove items without localization and one or more media items cannot be localized. + // This flag is used to display a model error when the field is required and every media item has been removed from it. + var mediaRemoved = false; + foreach (var item in itemsInField) { // negatives id whoud be localized var mediaItem = _contentManager.Get(item.Id, VersionOptions.Latest); @@ -55,7 +61,7 @@ namespace Orchard.MediaLibrary.Handlers { if (contentCulture == mediaCulture) { // The content culture and the media culture match mediaIds.Add(mediaItem.Id); - } + } else { if (mediaCulture == null) { // The media has not a culture, so it takes the content culture @@ -65,7 +71,7 @@ namespace Orchard.MediaLibrary.Handlers { "{0}: the media item {1} was culture neutral and it has been localized", field.DisplayName, mediaItem.As().FileName)); - } + } else { // The media has a culture var localizedMedia = _localizationServices.GetLocalizedContentItem(mediaItem, contentCulture); @@ -76,7 +82,7 @@ namespace Orchard.MediaLibrary.Handlers { "{0}: the media item {1} has been replaced by its localized version", field.DisplayName, mediaItem.As().FileName)); - } + } else { if (!settings.RemoveItemsWithoutLocalization) { // The media supports translations but have not a localized version, so it will be cloned in the right language @@ -92,33 +98,35 @@ namespace Orchard.MediaLibrary.Handlers { "{0}: a localized version of media item {1} has been created", field.DisplayName, mediaItem.As().FileName)); - } + } else { _orchardServices.Notifier.Warning(T( "{0}: the media item {1} has been removed from the field because its culture differs from content's culture", field.DisplayName, mediaItem.As().FileName)); + mediaRemoved = true; } } } } - } + } else if (mediaItem != null && !mediaIsLocalizable) { if (!settings.RemoveItemsWithNoLocalizationPart) { mediaIds.Add(mediaItem.Id); - } + } else { _orchardServices.Notifier.Warning(T( "{0}: the media item {1} has been removed from the field because culture neutral", field.DisplayName, mediaItem.As().FileName)); + mediaRemoved = true; } } } field.Ids = mediaIds.Distinct().ToArray(); - if (field.Ids.Length == 0 && fieldSettings.Required) { + if (field.Ids.Length == 0 && fieldSettings.Required && mediaRemoved) { context.Updater.AddModelError("Id", T("The {0} field is required.", field.DisplayName)); } } @@ -126,4 +134,4 @@ namespace Orchard.MediaLibrary.Handlers { } } } -} \ No newline at end of file +} From bdba35c704bd07b431fae901f0376856c9a743c2 Mon Sep 17 00:00:00 2001 From: Matteo Piovanelli Date: Fri, 30 Jun 2023 08:51:09 +0200 Subject: [PATCH 03/11] Fix bug in updating settings for fields (#8703) --- src/Orchard.Web/Modules/TinyMce/Settings/EditorEvents.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Orchard.Web/Modules/TinyMce/Settings/EditorEvents.cs b/src/Orchard.Web/Modules/TinyMce/Settings/EditorEvents.cs index f27f69727..dc9e78947 100644 --- a/src/Orchard.Web/Modules/TinyMce/Settings/EditorEvents.cs +++ b/src/Orchard.Web/Modules/TinyMce/Settings/EditorEvents.cs @@ -29,7 +29,7 @@ namespace TinyMce.Settings { } public override IEnumerable PartFieldEditorUpdate(ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) { - if (!_contentLinksDependenciesEnabled || !_htmlFields.Any(x => x.Equals(builder.Name, StringComparison.InvariantCultureIgnoreCase))) + if (!_contentLinksDependenciesEnabled || !_htmlFields.Any(x => x.Equals(builder.FieldType, StringComparison.InvariantCultureIgnoreCase))) yield break; var model = new ContentLinksSettings(); @@ -56,4 +56,4 @@ namespace TinyMce.Settings { yield return DefinitionTemplate(model); } } -} \ No newline at end of file +} From 729f8ddd75442d0bb6b5fc2ecfbcae7cadf88382 Mon Sep 17 00:00:00 2001 From: Matteo Piovanelli Date: Fri, 7 Jul 2023 09:58:17 +0200 Subject: [PATCH 04/11] Cache CultureRecords by both Id and Name (#8708) --- .../Services/DefaultCultureManager.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/Orchard/Localization/Services/DefaultCultureManager.cs b/src/Orchard/Localization/Services/DefaultCultureManager.cs index 49344b987..16c6ade47 100644 --- a/src/Orchard/Localization/Services/DefaultCultureManager.cs +++ b/src/Orchard/Localization/Services/DefaultCultureManager.cs @@ -62,12 +62,36 @@ namespace Orchard.Localization.Services { return _workContextAccessor.GetContext().CurrentCulture; } + protected Dictionary GetAllCulturesById() { + return _cacheManager.Get("all_culture_records_by_id", true, context => { + context.Monitor(_signals.When("culturesChanged")); + + return _cultureRepository.Table + .ToDictionary(cr => cr.Id); + }); + } public CultureRecord GetCultureById(int id) { - return _cultureRepository.Get(id); + var cultures = GetAllCulturesById(); + CultureRecord result; + cultures.TryGetValue(id, out result); + + return result; } + protected Dictionary GetAllCulturesByName() { + return _cacheManager.Get("all_culture_records_by_name", true, context => { + context.Monitor(_signals.When("culturesChanged")); + + return _cultureRepository.Table + .ToDictionary(cr => cr.Culture); + }); + } public CultureRecord GetCultureByName(string cultureName) { - return _cultureRepository.Get(cr => cr.Culture == cultureName); + var cultures = GetAllCulturesByName(); + CultureRecord result; + cultures.TryGetValue(cultureName, out result); + + return result; } public string GetSiteCulture() { From 01ba3cabbc12e893053f2301df52ae70f35a1c03 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Wed, 12 Jul 2023 16:41:14 +0200 Subject: [PATCH 05/11] Grabbing the contents of the compile workflow from 1.10.x --- .github/workflows/compile.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 5e685c9b3..8b4518b86 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -2,15 +2,32 @@ name: Compile on: workflow_dispatch: + pull_request: + push: + branches: + - dev + - 1.10.x jobs: compile: name: Compile defaults: run: - shell: cmd + shell: pwsh runs-on: windows-latest steps: + - name: Clone repository + uses: actions/checkout@v3.1.0 + + - name: Restore NuGet packages + run: nuget restore src/Orchard.sln + + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.3.1 + + - name: Compile + run: msbuild Orchard.proj /m /t:Compile /p:MvcBuildViews=true /p:TreatWarningsAsErrors=true -WarnAsError + - name: Test - run: echo Test + run: msbuild Orchard.proj /m /t:Test From 0c3afcd93e6d935f7bbd973888b53e7e760b5820 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Wed, 12 Jul 2023 16:58:03 +0200 Subject: [PATCH 06/11] Revert "Grabbing the contents of the compile workflow from 1.10.x" This reverts commit 01ba3cabbc12e893053f2301df52ae70f35a1c03. --- .github/workflows/compile.yml | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 8b4518b86..5e685c9b3 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -2,32 +2,15 @@ name: Compile on: workflow_dispatch: - pull_request: - push: - branches: - - dev - - 1.10.x jobs: compile: name: Compile defaults: run: - shell: pwsh + shell: cmd runs-on: windows-latest steps: - - name: Clone repository - uses: actions/checkout@v3.1.0 - - - name: Restore NuGet packages - run: nuget restore src/Orchard.sln - - - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.3.1 - - - name: Compile - run: msbuild Orchard.proj /m /t:Compile /p:MvcBuildViews=true /p:TreatWarningsAsErrors=true -WarnAsError - - name: Test - run: msbuild Orchard.proj /m /t:Test + run: echo Test From 4043df7a0d158dfc70cea65480b61fbd8191bbb3 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Wed, 12 Jul 2023 16:59:34 +0200 Subject: [PATCH 07/11] Consolidating Microsoft.Owin package versions (#8711) --- src/Orchard.Specs/Hosting/Orchard.Web/Web.config | 4 ++-- .../Orchard.ContentPreview/Orchard.ContentPreview.csproj | 8 ++++---- .../Modules/Orchard.ContentPreview/packages.config | 4 ++-- .../Modules/Orchard.Glimpse/Orchard.Glimpse.csproj | 8 ++++---- src/Orchard.Web/Modules/Orchard.Glimpse/packages.config | 4 ++-- .../Orchard.MediaLibrary.WebSearch.csproj | 8 ++++---- .../Orchard.MediaLibrary.WebSearch/packages.config | 4 ++-- .../Modules/Orchard.OpenId/Orchard.OpenId.csproj | 4 ++-- src/Orchard.Web/Modules/Orchard.OpenId/packages.config | 2 +- src/Orchard.Web/Orchard.Web.csproj | 4 ++-- src/Orchard.Web/Web.config | 4 ++-- src/Orchard.Web/packages.config | 2 +- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Orchard.Specs/Hosting/Orchard.Web/Web.config b/src/Orchard.Specs/Hosting/Orchard.Web/Web.config index 2d7f75727..2f2df5a9d 100644 --- a/src/Orchard.Specs/Hosting/Orchard.Web/Web.config +++ b/src/Orchard.Specs/Hosting/Orchard.Web/Web.config @@ -53,9 +53,9 @@ - + - + diff --git a/src/Orchard.Web/Modules/Orchard.ContentPreview/Orchard.ContentPreview.csproj b/src/Orchard.Web/Modules/Orchard.ContentPreview/Orchard.ContentPreview.csproj index f1b75fc1e..2bfa1923c 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentPreview/Orchard.ContentPreview.csproj +++ b/src/Orchard.Web/Modules/Orchard.ContentPreview/Orchard.ContentPreview.csproj @@ -62,11 +62,11 @@ ..\..\..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.1.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.2.2\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - ..\..\..\packages\Microsoft.Owin.Security.4.1.1\lib\net45\Microsoft.Owin.Security.dll + + ..\..\..\packages\Microsoft.Owin.Security.4.2.2\lib\net45\Microsoft.Owin.Security.dll ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll diff --git a/src/Orchard.Web/Modules/Orchard.ContentPreview/packages.config b/src/Orchard.Web/Modules/Orchard.ContentPreview/packages.config index 29f319139..8d3574a41 100644 --- a/src/Orchard.Web/Modules/Orchard.ContentPreview/packages.config +++ b/src/Orchard.Web/Modules/Orchard.ContentPreview/packages.config @@ -6,8 +6,8 @@ - - + + diff --git a/src/Orchard.Web/Modules/Orchard.Glimpse/Orchard.Glimpse.csproj b/src/Orchard.Web/Modules/Orchard.Glimpse/Orchard.Glimpse.csproj index 6e7d478b4..37378b2d2 100644 --- a/src/Orchard.Web/Modules/Orchard.Glimpse/Orchard.Glimpse.csproj +++ b/src/Orchard.Web/Modules/Orchard.Glimpse/Orchard.Glimpse.csproj @@ -86,11 +86,11 @@ ..\..\..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.1.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.2.2\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - ..\..\..\packages\Microsoft.Owin.Security.4.1.1\lib\net45\Microsoft.Owin.Security.dll + + ..\..\..\packages\Microsoft.Owin.Security.4.2.2\lib\net45\Microsoft.Owin.Security.dll ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll diff --git a/src/Orchard.Web/Modules/Orchard.Glimpse/packages.config b/src/Orchard.Web/Modules/Orchard.Glimpse/packages.config index af90045df..7c722f847 100644 --- a/src/Orchard.Web/Modules/Orchard.Glimpse/packages.config +++ b/src/Orchard.Web/Modules/Orchard.Glimpse/packages.config @@ -14,8 +14,8 @@ - - + + diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/Orchard.MediaLibrary.WebSearch.csproj b/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/Orchard.MediaLibrary.WebSearch.csproj index e4410183d..4296c3ee0 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/Orchard.MediaLibrary.WebSearch.csproj +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/Orchard.MediaLibrary.WebSearch.csproj @@ -62,11 +62,11 @@ ..\..\..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.1.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.2.2\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - ..\..\..\packages\Microsoft.Owin.Security.4.1.1\lib\net45\Microsoft.Owin.Security.dll + + ..\..\..\packages\Microsoft.Owin.Security.4.2.2\lib\net45\Microsoft.Owin.Security.dll ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll diff --git a/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/packages.config b/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/packages.config index 4fac78f7d..606c316be 100644 --- a/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/packages.config +++ b/src/Orchard.Web/Modules/Orchard.MediaLibrary.WebSearch/packages.config @@ -8,8 +8,8 @@ - - + + diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj b/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj index 8879f7d5e..9c7ec2e32 100644 --- a/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj @@ -108,8 +108,8 @@ ..\..\..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll - - ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.1.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.4.2.2\lib\net45\Microsoft.Owin.Host.SystemWeb.dll ..\..\..\packages\Microsoft.Owin.Security.4.2.2\lib\net45\Microsoft.Owin.Security.dll diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/packages.config b/src/Orchard.Web/Modules/Orchard.OpenId/packages.config index 7689a40f2..fe9b76276 100644 --- a/src/Orchard.Web/Modules/Orchard.OpenId/packages.config +++ b/src/Orchard.Web/Modules/Orchard.OpenId/packages.config @@ -19,7 +19,7 @@ - + diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj index 398eafa46..c61e189ac 100644 --- a/src/Orchard.Web/Orchard.Web.csproj +++ b/src/Orchard.Web/Orchard.Web.csproj @@ -75,8 +75,8 @@ ..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll - - ..\packages\Microsoft.Owin.Host.SystemWeb.4.1.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\packages\Microsoft.Owin.Host.SystemWeb.4.2.2\lib\net45\Microsoft.Owin.Host.SystemWeb.dll ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 1a3663be8..deb52a5cc 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -50,7 +50,7 @@ - + @@ -245,7 +245,7 @@ - + diff --git a/src/Orchard.Web/packages.config b/src/Orchard.Web/packages.config index 8e0c885e5..cc1deeb50 100644 --- a/src/Orchard.Web/packages.config +++ b/src/Orchard.Web/packages.config @@ -10,7 +10,7 @@ - + From 8793d4d66380c73f96f54921d21fd6e9f3aa9983 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Fri, 14 Jul 2023 15:33:02 +0200 Subject: [PATCH 08/11] Resolving conflict between 1.10.x and dev in the order of migration steps in Core/Common/Migrations.cs --- src/Orchard.Web/Core/Common/Migrations.cs | 75 ++++++++++++++++++++--- src/Orchard.Web/Core/Orchard.Core.csproj | 4 ++ src/Orchard.Web/Core/packages.config | 1 + 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index 412dc3149..7cad00828 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -1,4 +1,5 @@ using System; +using System.Data; using System.Linq; using Orchard.ContentManagement.MetaData; using Orchard.Core.Common.Models; @@ -9,10 +10,12 @@ using Orchard.Data.Migration; namespace Orchard.Core.Common { public class Migrations : DataMigrationImpl { private readonly IRepository _identityPartRepository; + private readonly ISessionFactoryHolder _sessionFactoryHolder; - public Migrations(IRepository identityPartRepository) { + public Migrations(IRepository identityPartRepository, ISessionFactoryHolder sessionFactoryHolder) { _identityPartRepository = identityPartRepository; + _sessionFactoryHolder = sessionFactoryHolder; } @@ -155,24 +158,60 @@ namespace Orchard.Core.Common { return 6; } + // When upgrading from version 6 of 1.10.x, we'll just execute the same steps, but in a different order. public int UpdateFrom6() { - SchemaBuilder.AlterTable(nameof(IdentityPartRecord), table => table - .CreateIndex($"IDX_{nameof(IdentityPartRecord)}_{nameof(IdentityPartRecord.Identifier)}", nameof(IdentityPartRecord.Identifier))); + // This is the original step of the dev branch. + AddIndexForIdentityPartRecordIdentifier(); return 7; } public int UpdateFrom7() { - // The Container_Id is basically a foreign key, used in several queries - SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => { - table.CreateIndex($"IDX_{nameof(CommonPartRecord)}_Container_id", - "Container_id"); - }); + // This is the original step of the dev branch. + AddIndexForCommonPartRecordContainerId(); + + // When upgrading from version 7 of 1.10.x, this index isn't created yet, so we need to run this step + // "again". On the other hand, AddIndexesForCommonPartOwner in UpdateFrom8 won't do anything, because those + // indexes were added in the 1.10.x version of UpdateFrom6. + AddIndexForIdentityPartRecordIdentifier(); return 8; } public int UpdateFrom8() { + // This is the original step of the dev branch. + AddIndexesForCommonPartOwner(); + + // When upgrading from version 8 of 1.10.x, this index isn't created yet, so we need to run this step + // "again" + AddIndexForCommonPartRecordContainerId(); + + return 9; + } + + // This change was originally UpdateFrom7 on 1.10.x and UpdateFrom6 on dev. + private void AddIndexForIdentityPartRecordIdentifier() { + var indexName = $"IDX_{nameof(IdentityPartRecord)}_{nameof(IdentityPartRecord.Identifier)}"; + + if (IndexExists(nameof(IdentityPartRecord), indexName)) return; + + SchemaBuilder.AlterTable(nameof(IdentityPartRecord), table => table.CreateIndex( + indexName, + nameof(IdentityPartRecord.Identifier))); + } + + // This change was originally UpdateFrom8 on 1.10.x and UpdateFrom7 on dev. + private void AddIndexForCommonPartRecordContainerId() { + var indexName = $"IDX_{nameof(CommonPartRecord)}_Container_id"; + + if (IndexExists(nameof(CommonPartRecord), indexName)) return; + + // Container_Id is used in several queries like a foreign key. + SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => table.CreateIndex(indexName, "Container_id")); + } + + // This change was originally UpdateFrom6 on 1.10.x and UpdateFrom8 on dev. + private void AddIndexesForCommonPartOwner() { // Studying SQL Server query execution plans we noticed that when the system // tries to find content items for requests such as // "The items of type TTT owned by me, ordered from the most recent" @@ -198,9 +237,12 @@ namespace Orchard.Core.Common { // and this_.Published = 1 // ORDER BY // commonpart2_PublishedUtc desc + var createdUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByCreation"; + + if (IndexExists(nameof(CommonPartRecord), createdUtcIndexName)) return; SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => { - table.CreateIndex($"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByCreation", + table.CreateIndex(createdUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.CreatedUtc)); table.CreateIndex($"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByModification", @@ -210,8 +252,21 @@ namespace Orchard.Core.Common { nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.PublishedUtc)); }); + } - return 9; + private bool IndexExists(string tableName, string indexName) { + // Database-agnostic way of checking the existence of an index. + using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) { + var connection = session.Connection; + + if (connection == null) { + throw new InvalidOperationException("The database connection object should derive from DbConnection to check if a table exists."); + } + + return connection.GetSchema("Indexes").Rows.Cast().Any(row => + row["TABLE_NAME"].ToString() == SchemaBuilder.TableDbName(tableName) + && row["INDEX_NAME"].ToString() == indexName); + } } } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index 119c9c427..aea063fc7 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -63,10 +63,14 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + ..\..\packages\NHibernate.5.3.10\lib\net461\NHibernate.dll + 3.5 + diff --git a/src/Orchard.Web/Core/packages.config b/src/Orchard.Web/Core/packages.config index 0938ac40a..c9ae65df2 100644 --- a/src/Orchard.Web/Core/packages.config +++ b/src/Orchard.Web/Core/packages.config @@ -5,4 +5,5 @@ + From 3d389df858532e5de47dd4aaf5f86b3c93422eff Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Fri, 14 Jul 2023 19:53:45 +0200 Subject: [PATCH 09/11] Fixing IndexExists for tenants with a table prefix --- src/Orchard.Web/Core/Common/Migrations.cs | 35 ++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index 7cad00828..b2a776636 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -1,21 +1,31 @@ using System; +using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Linq; using Orchard.ContentManagement.MetaData; using Orchard.Core.Common.Models; using Orchard.Core.Contents.Extensions; using Orchard.Data; using Orchard.Data.Migration; +using Orchard.Environment.Configuration; namespace Orchard.Core.Common { public class Migrations : DataMigrationImpl { private readonly IRepository _identityPartRepository; private readonly ISessionFactoryHolder _sessionFactoryHolder; + private readonly ShellSettings _shellSettings; + + private IList existingIndexNames; - public Migrations(IRepository identityPartRepository, ISessionFactoryHolder sessionFactoryHolder) { + public Migrations( + IRepository identityPartRepository, + ISessionFactoryHolder sessionFactoryHolder, + ShellSettings shellSettings) { _identityPartRepository = identityPartRepository; _sessionFactoryHolder = sessionFactoryHolder; + _shellSettings = shellSettings; } @@ -255,18 +265,23 @@ namespace Orchard.Core.Common { } private bool IndexExists(string tableName, string indexName) { - // Database-agnostic way of checking the existence of an index. - using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) { - var connection = session.Connection; + if (existingIndexNames == null) { + // Database-agnostic way of checking the existence of an index. + using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) { + var connection = session.Connection; - if (connection == null) { - throw new InvalidOperationException("The database connection object should derive from DbConnection to check if a table exists."); + if (connection == null) { + throw new InvalidOperationException("The database connection object should derive from DbConnection to check if a table exists."); + } + + existingIndexNames = connection.GetSchema("Indexes").Rows.Cast().Select(row => + $"{row["TABLE_NAME"]}.{row["INDEX_NAME"]}").ToList(); } - - return connection.GetSchema("Indexes").Rows.Cast().Any(row => - row["TABLE_NAME"].ToString() == SchemaBuilder.TableDbName(tableName) - && row["INDEX_NAME"].ToString() == indexName); } + + var indexNamePrefix = string.IsNullOrEmpty(_shellSettings.DataTablePrefix) + ? string.Empty : $"{_shellSettings.DataTablePrefix}_"; + return existingIndexNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{indexNamePrefix}{indexName}"); } } } \ No newline at end of file From 37ab0cd8d1cfb43ec2163e8da5c0566a706983bb Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Tue, 18 Jul 2023 19:18:20 +0200 Subject: [PATCH 10/11] Updating IndexExists logic to only check indexes that belong to the current tenant (except for Default) --- src/Orchard.Web/Core/Common/Migrations.cs | 46 +++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index b2a776636..5cd1d81c8 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Linq; using Orchard.ContentManagement.MetaData; using Orchard.Core.Common.Models; @@ -16,7 +15,7 @@ namespace Orchard.Core.Common { private readonly ISessionFactoryHolder _sessionFactoryHolder; private readonly ShellSettings _shellSettings; - private IList existingIndexNames; + private HashSet _existingIndexNames = new HashSet(); public Migrations( @@ -208,6 +207,8 @@ namespace Orchard.Core.Common { SchemaBuilder.AlterTable(nameof(IdentityPartRecord), table => table.CreateIndex( indexName, nameof(IdentityPartRecord.Identifier))); + + _existingIndexNames.Add(indexName); } // This change was originally UpdateFrom8 on 1.10.x and UpdateFrom7 on dev. @@ -218,6 +219,8 @@ namespace Orchard.Core.Common { // Container_Id is used in several queries like a foreign key. SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => table.CreateIndex(indexName, "Container_id")); + + _existingIndexNames.Add(indexName); } // This change was originally UpdateFrom6 on 1.10.x and UpdateFrom8 on dev. @@ -248,40 +251,43 @@ namespace Orchard.Core.Common { // ORDER BY // commonpart2_PublishedUtc desc var createdUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByCreation"; + var modifiedUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByModification"; + var publishedUtcIndexName = $"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByPublication"; if (IndexExists(nameof(CommonPartRecord), createdUtcIndexName)) return; SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => { - table.CreateIndex(createdUtcIndexName, - nameof(CommonPartRecord.OwnerId), - nameof(CommonPartRecord.CreatedUtc)); - table.CreateIndex($"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByModification", - nameof(CommonPartRecord.OwnerId), - nameof(CommonPartRecord.ModifiedUtc)); - table.CreateIndex($"IDX_{nameof(CommonPartRecord)}_OwnedBy_ByPublication", - nameof(CommonPartRecord.OwnerId), - nameof(CommonPartRecord.PublishedUtc)); + table.CreateIndex(createdUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.CreatedUtc)); + table.CreateIndex(modifiedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.ModifiedUtc)); + table.CreateIndex(publishedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.PublishedUtc)); }); + + _existingIndexNames.Add(createdUtcIndexName); + _existingIndexNames.Add(modifiedUtcIndexName); + _existingIndexNames.Add(publishedUtcIndexName); } private bool IndexExists(string tableName, string indexName) { - if (existingIndexNames == null) { + var tenantTablesPrefix = string.IsNullOrEmpty(_shellSettings.DataTablePrefix) + ? string.Empty : $"{_shellSettings.DataTablePrefix}_"; + + if (!_existingIndexNames.Any()) { // Database-agnostic way of checking the existence of an index. using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) { - var connection = session.Connection; + var connection = session.Connection ?? throw new InvalidOperationException( + "The database connection object should derive from DbConnection to check if an index exists."); - if (connection == null) { - throw new InvalidOperationException("The database connection object should derive from DbConnection to check if a table exists."); + var indexes = connection.GetSchema("Indexes").Rows.Cast(); + + if (!string.IsNullOrEmpty(tenantTablesPrefix)) { + indexes = indexes.Where(row => row["TABLE_NAME"].ToString().StartsWith(tenantTablesPrefix)); } - existingIndexNames = connection.GetSchema("Indexes").Rows.Cast().Select(row => - $"{row["TABLE_NAME"]}.{row["INDEX_NAME"]}").ToList(); + _existingIndexNames = indexes.Select(row => $"{row["TABLE_NAME"]}.{row["INDEX_NAME"]}").ToHashSet(); } } - var indexNamePrefix = string.IsNullOrEmpty(_shellSettings.DataTablePrefix) - ? string.Empty : $"{_shellSettings.DataTablePrefix}_"; - return existingIndexNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{indexNamePrefix}{indexName}"); + return _existingIndexNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{tenantTablesPrefix}{indexName}"); } } } \ No newline at end of file From 01b3f2429f4c86df73d6847ff34c0807cc7f6067 Mon Sep 17 00:00:00 2001 From: Benedek Farkas Date: Tue, 18 Jul 2023 22:05:59 +0200 Subject: [PATCH 11/11] Resolving Migration step conflict for Orchard.Projections too, fixing logic Orchard.Core/Common --- src/Orchard.Web/Core/Common/Migrations.cs | 38 +++++---- .../Modules/Orchard.Projections/Migrations.cs | 77 ++++++++++++++++--- 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index 5cd1d81c8..f331ecd80 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -167,7 +167,8 @@ namespace Orchard.Core.Common { return 6; } - // When upgrading from version 6 of 1.10.x, we'll just execute the same steps, but in a different order. + // When upgrading from version 6 of 1.10.x (up until version 9), we'll just execute the same steps, but in a + // different order. public int UpdateFrom6() { // This is the original step of the dev branch. AddIndexForIdentityPartRecordIdentifier(); @@ -208,7 +209,7 @@ namespace Orchard.Core.Common { indexName, nameof(IdentityPartRecord.Identifier))); - _existingIndexNames.Add(indexName); + IndexCreated(nameof(IdentityPartRecord), indexName); } // This change was originally UpdateFrom8 on 1.10.x and UpdateFrom7 on dev. @@ -220,22 +221,18 @@ namespace Orchard.Core.Common { // Container_Id is used in several queries like a foreign key. SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => table.CreateIndex(indexName, "Container_id")); - _existingIndexNames.Add(indexName); + IndexCreated(nameof(CommonPartRecord), indexName); } // This change was originally UpdateFrom6 on 1.10.x and UpdateFrom8 on dev. private void AddIndexesForCommonPartOwner() { - // Studying SQL Server query execution plans we noticed that when the system - // tries to find content items for requests such as - // "The items of type TTT owned by me, ordered from the most recent" - // the existing indexes are not used. SQL Server does an index scan on the - // Primary key for CommonPartRecord. This may lead to annoying deadlocks when - // there are two concurrent transactions that are doing both this kind of query - // as well as an update (or insert) in the CommonPartRecord. - // Tests show that this can be easily fixed by adding a non-clustered index - // with these keys: OwnerId, {one of PublishedUTC, ModifiedUTC, CreatedUTC}. - // That means we need three indexes (one for each DateTime) to support ordering - // on either of them. + // Studying SQL Server query execution plans we noticed that when the system tries to find content items for + // requests such as "The items of type TTT owned by me, ordered from the most recent" the existing indexes + // are not used. SQL Server does an index scan on the Primary key for CommonPartRecord. This may lead to + // annoying deadlocks when there are two concurrent transactions that are doing both this kind of query as + // well as an update (or insert) in the CommonPartRecord. Tests show that this can be easily fixed by adding + // a non-clustered index with these keys: OwnerId, {one of PublishedUTC, ModifiedUTC, CreatedUTC}. That + // means we need three indexes (one for each DateTime) to support ordering on either of them. // The queries we analyzed look like (in pseudo sql) // SELECT TOP (N) * @@ -262,9 +259,9 @@ namespace Orchard.Core.Common { table.CreateIndex(publishedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.PublishedUtc)); }); - _existingIndexNames.Add(createdUtcIndexName); - _existingIndexNames.Add(modifiedUtcIndexName); - _existingIndexNames.Add(publishedUtcIndexName); + IndexCreated(nameof(CommonPartRecord), createdUtcIndexName); + IndexCreated(nameof(CommonPartRecord), modifiedUtcIndexName); + IndexCreated(nameof(CommonPartRecord), publishedUtcIndexName); } private bool IndexExists(string tableName, string indexName) { @@ -289,5 +286,12 @@ namespace Orchard.Core.Common { return _existingIndexNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{tenantTablesPrefix}{indexName}"); } + + private void IndexCreated(string tableName, string indexName) { + var tenantTablesPrefix = string.IsNullOrEmpty(_shellSettings.DataTablePrefix) + ? string.Empty : $"{_shellSettings.DataTablePrefix}_"; + + _existingIndexNames.Add($"{SchemaBuilder.TableDbName(tableName)}.{tenantTablesPrefix}{indexName}"); + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs index 282d8a35b..9c413a325 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Linq; using Orchard.ContentManagement.MetaData; @@ -7,6 +8,7 @@ using Orchard.Core.Contents.Extensions; using Orchard.Core.Title.Models; using Orchard.Data; using Orchard.Data.Migration; +using Orchard.Environment.Configuration; using Orchard.Localization; using Orchard.Projections.Models; @@ -15,14 +17,22 @@ namespace Orchard.Projections { private readonly IRepository _memberBindingRepository; private readonly IRepository _layoutRepository; private readonly IRepository _propertyRecordRepository; + private readonly ISessionFactoryHolder _sessionFactoryHolder; + private readonly ShellSettings _shellSettings; + + private HashSet _existingColumnNames = new HashSet(); public Migrations( IRepository memberBindingRepository, IRepository layoutRepository, - IRepository propertyRecordRepository) { + IRepository propertyRecordRepository, + ISessionFactoryHolder sessionFactoryHolder, + ShellSettings shellSettings) { _memberBindingRepository = memberBindingRepository; _layoutRepository = layoutRepository; _propertyRecordRepository = propertyRecordRepository; + _sessionFactoryHolder = sessionFactoryHolder; + _shellSettings = shellSettings; T = NullLocalizer.Instance; } @@ -356,32 +366,79 @@ namespace Orchard.Projections { return 5; } -#pragma warning disable CS0618 - // disable compiler warning regarding the fact that RewriteOutput is obsolete - // because this migration is handling just that. + // When upgrading from version 5 of 1.10.x (up until version 7), we'll just execute the same steps, but in a + // different order. public int UpdateFrom5() { + // This is the original step of the dev branch. + MigratePropertyRecordToRewriteOutputCondition(); + + return 6; + } + + public int UpdateFrom6() { + // This is the original step of the dev branch. + AddLayoutRecordGuid(); + + // When upgrading from version 6 of 1.10.x, this column isn't created yet, so we need to run this step + // "again". + MigratePropertyRecordToRewriteOutputCondition(); + + return 7; + } + + // This change was originally UpdateFrom5 on dev (but didn't exist on 1.10.x). + private void MigratePropertyRecordToRewriteOutputCondition() { + if (ColumnExists("PropertyRecord", "RewriteOutputCondition")) return; + SchemaBuilder.AlterTable("PropertyRecord", table => table .AddColumn("RewriteOutputCondition", c => c.Unlimited()) ); foreach (var property in _propertyRecordRepository.Table) +#pragma warning disable CS0618 // Type or member is obsolete + // Reading this obsolete property to migrate its data to a new one. if (property.RewriteOutput) property.RewriteOutputCondition = "true"; +#pragma warning restore CS0618 // Type or member is obsolete - return 6; + ColumnAdded("PropertyRecord", "RewriteOutputCondition"); } -#pragma warning restore CS0618 - public int UpdateFrom6() { - SchemaBuilder.AlterTable("LayoutRecord", t => t.AddColumn("GUIdentifier", - column => column.WithLength(68))); + // This change was originally UpdateFrom5 on 1.10.x and UpdateFrom6 on dev. + private void AddLayoutRecordGuid() { + if (ColumnExists("LayoutRecord", "GUIdentifier")) return; + + SchemaBuilder.AlterTable("LayoutRecord", table => + table.AddColumn("GUIdentifier", column => column.WithLength(68))); var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList(); foreach (var layout in layoutRecords) { layout.GUIdentifier = Guid.NewGuid().ToString(); } - return 7; + ColumnAdded("LayoutRecord", "GUIdentifier"); } + private bool ColumnExists(string tableName, string columnName) { + if (!_existingColumnNames.Any()) { + // Database-agnostic way of checking the existence of a column. + using (var session = _sessionFactoryHolder.GetSessionFactory().OpenSession()) { + var connection = session.Connection ?? throw new InvalidOperationException( + "The database connection object should derive from DbConnection to check if a column exists."); + + var columns = connection.GetSchema("Columns").Rows.Cast(); + + if (!string.IsNullOrEmpty(_shellSettings.DataTablePrefix)) { + columns = columns.Where(row => row["TABLE_NAME"].ToString().StartsWith($"{_shellSettings.DataTablePrefix}_")); + } + + _existingColumnNames = columns.Select(row => $"{row["TABLE_NAME"]}.{row["COLUMN_NAME"]}").ToHashSet(); + } + } + + return _existingColumnNames.Contains($"{SchemaBuilder.TableDbName(tableName)}.{columnName}"); + } + + private void ColumnAdded(string tableName, string columnName) => + _existingColumnNames.Add($"{SchemaBuilder.TableDbName(tableName)}.{columnName}"); } }