diff --git a/src/Orchard.Web/Core/Common/Migrations.cs b/src/Orchard.Web/Core/Common/Migrations.cs index f331ecd80..65565f045 100644 --- a/src/Orchard.Web/Core/Common/Migrations.cs +++ b/src/Orchard.Web/Core/Common/Migrations.cs @@ -1,30 +1,27 @@ using System; -using System.Collections.Generic; -using System.Data; 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 HashSet _existingIndexNames = new HashSet(); + /// + /// When upgrading from "1.10.x" branch code committed after 1.10.3 to "dev" branch code or 1.11, merge + /// conflicts between "1.10.x" and "dev" caused by running the same migration steps in a different order need to + /// be resolved by instructing this migration to decide which steps need to be executed. If you're upgrading + /// under these conditions and your pre-upgrade migration version is 7 or 8, use HostComponents.config to + /// override one of these properties to true. + /// + public bool IsUpgradingFromOrchard_1_10_x_Version_7 { get; set; } + public bool IsUpgradingFromOrchard_1_10_x_Version_8 { get; set; } - - public Migrations( - IRepository identityPartRepository, - ISessionFactoryHolder sessionFactoryHolder, - ShellSettings shellSettings) { + public Migrations(IRepository identityPartRepository) { _identityPartRepository = identityPartRepository; - _sessionFactoryHolder = sessionFactoryHolder; - _shellSettings = shellSettings; } @@ -167,34 +164,59 @@ namespace Orchard.Core.Common { return 6; } - // 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(); return 7; } public int UpdateFrom7() { - // 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(); + if (IsUpgradingFromOrchard_1_10_x_Version_7) { + AddIndexForIdentityPartRecordIdentifier(); + } + else if (IsUpgradingFromOrchard_1_10_x_Version_8) { + AddIndexForCommonPartRecordContainerId(); + } + else { + // This change was originally UpdateFrom6 on 1.10.x and UpdateFrom8 on dev. - // When upgrading from version 8 of 1.10.x, this index isn't created yet, so we need to run this step - // "again" - AddIndexForCommonPartRecordContainerId(); + // 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) * + // FROM + // ContentItemVersionRecord this_ + // inner join ContentItemRecord contentite1_ on this_.ContentItemRecord_id=contentite1_.Id + // inner join CommonPartRecord commonpart2_ on contentite1_.Id=commonpart2.Id + // left outer join ContentTypeRecord contenttyp6_ on contentite1_.ContentType_id=contenttyp6_.Id + // WHERE + // contentite1.ContentType_id = {TTT} + // and commonpart2_.OwnerId = {userid} + // and this_.Published = 1 + // 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"; + + SchemaBuilder.AlterTable(nameof(CommonPartRecord), table => { + 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)); + }); + } return 9; } @@ -203,95 +225,17 @@ namespace Orchard.Core.Common { 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))); - - IndexCreated(nameof(IdentityPartRecord), indexName); } // 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")); - - 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. - - // The queries we analyzed look like (in pseudo sql) - // SELECT TOP (N) * - // FROM - // ContentItemVersionRecord this_ - // inner join ContentItemRecord contentite1_ on this_.ContentItemRecord_id=contentite1_.Id - // inner join CommonPartRecord commonpart2_ on contentite1_.Id=commonpart2.Id - // left outer join ContentTypeRecord contenttyp6_ on contentite1_.ContentType_id=contenttyp6_.Id - // WHERE - // contentite1.ContentType_id = {TTT} - // and commonpart2_.OwnerId = {userid} - // and this_.Published = 1 - // 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(modifiedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.ModifiedUtc)); - table.CreateIndex(publishedUtcIndexName, nameof(CommonPartRecord.OwnerId), nameof(CommonPartRecord.PublishedUtc)); - }); - - IndexCreated(nameof(CommonPartRecord), createdUtcIndexName); - IndexCreated(nameof(CommonPartRecord), modifiedUtcIndexName); - IndexCreated(nameof(CommonPartRecord), publishedUtcIndexName); - } - - private bool IndexExists(string tableName, string indexName) { - 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 ?? throw new InvalidOperationException( - "The database connection object should derive from DbConnection to check if an index exists."); - - var indexes = connection.GetSchema("Indexes").Rows.Cast(); - - if (!string.IsNullOrEmpty(tenantTablesPrefix)) { - indexes = indexes.Where(row => row["TABLE_NAME"].ToString().StartsWith(tenantTablesPrefix)); - } - - _existingIndexNames = indexes.Select(row => $"{row["TABLE_NAME"]}.{row["INDEX_NAME"]}").ToHashSet(); - } - } - - 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.CustomForms/Drivers/CustomFormPartDriver.cs b/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs index 28d8a6ea3..4fe5d8899 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Drivers/CustomFormPartDriver.cs @@ -11,20 +11,26 @@ using System; using Orchard.Core.Contents.Settings; using Orchard.Security; using Orchard.Localization; +using Orchard.CustomForms.Services; namespace Orchard.CustomForms.Drivers { public class CustomFormPartDriver : ContentPartDriver { private readonly IContentDefinitionManager _contentDefinitionManager; private readonly IOrchardServices _orchardServices; private readonly IAuthorizationService _authService; + private readonly IEditorBuilderWrapper _editorBuilderWrapper; public CustomFormPartDriver( IContentDefinitionManager contentDefinitionManager, IOrchardServices orchardServices, - IAuthorizationService authService) { + IAuthorizationService authService, + IEditorBuilderWrapper editorBuilderWrapper) { + _contentDefinitionManager = contentDefinitionManager; _orchardServices = orchardServices; _authService = authService; + _editorBuilderWrapper = editorBuilderWrapper; + T = NullLocalizer.Instance; } @@ -32,39 +38,43 @@ namespace Orchard.CustomForms.Drivers { protected override DriverResult Display(CustomFormPart part, string displayType, dynamic shapeHelper) { // this method is used by the widget to render the form when it is displayed + // Display CustomForm_Wrapper shape only if shape type is Detail. + if (displayType.Equals("Detail")) { + int contentId = 0; + var queryString = _orchardServices.WorkContext.HttpContext.Request.QueryString; - int contentId = 0; - var queryString = _orchardServices.WorkContext.HttpContext.Request.QueryString; + if (queryString.AllKeys.Contains("contentId")) { + int.TryParse(queryString["contentId"], out contentId); + } - if (queryString.AllKeys.Contains("contentId")) { - int.TryParse(queryString["contentId"], out contentId); - } + ContentItem contentItem; + if (contentId > 0) { + contentItem = _orchardServices.ContentManager.Get(contentId); - ContentItem contentItem; - if (contentId > 0) { - contentItem = _orchardServices.ContentManager.Get(contentId); + if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.EditContent, contentItem)) + return null; + } else { + contentItem = _orchardServices.ContentManager.New(part.ContentType); - if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.EditContent, contentItem)) + if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.CreateContent, contentItem)) + return null; + } + + if (contentItem == null || contentItem.ContentType != part.ContentType) return null; - } else { - contentItem = _orchardServices.ContentManager.New(part.ContentType); - if (part.UseContentTypePermissions && !_orchardServices.Authorizer.Authorize(Core.Contents.Permissions.CreateContent, contentItem)) + if (!contentItem.Has()) { return null; + } + + return ContentShape("Parts_CustomForm_Wrapper", () => { + return shapeHelper.Parts_CustomForm_Wrapper() + .Editor(_editorBuilderWrapper.BuildEditor(contentItem)) + .ContentPart(part); + }); } - - if (contentItem == null || contentItem.ContentType != part.ContentType) - return null; - - if (!contentItem.Has()) { - return null; - } - - return ContentShape("Parts_CustomForm_Wrapper", () => { - return shapeHelper.Parts_CustomForm_Wrapper() - .Editor(_orchardServices.ContentManager.BuildEditor(contentItem)) - .ContentPart(part); - }); + // Returning null avoids rendering the edit shape for current custom form. + return null; } protected override DriverResult Editor(CustomFormPart part, dynamic shapeHelper) { diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj b/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj index 3f38e0295..43c8c516d 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Orchard.CustomForms.csproj @@ -146,6 +146,8 @@ + + @@ -231,4 +233,4 @@ - + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs new file mode 100644 index 000000000..5ec318a04 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/EditorBuilderWrapper.cs @@ -0,0 +1,22 @@ +using Orchard.ContentManagement; + +namespace Orchard.CustomForms.Services { + public class EditorBuilderWrapper : IEditorBuilderWrapper { + + private readonly IContentManager _contentManager; + + public EditorBuilderWrapper( + IContentManager contentManager) { + + _contentManager = contentManager; + } + + public dynamic BuildEditor(IContent content) { + return _contentManager.BuildEditor(content); + } + + public dynamic UpdateEditor(IContent content, IUpdateModel updateModel) { + return _contentManager.UpdateEditor(content, updateModel); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs new file mode 100644 index 000000000..8b9978b41 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Services/IEditorBuilderWrapper.cs @@ -0,0 +1,8 @@ +using Orchard.ContentManagement; + +namespace Orchard.CustomForms.Services { + public interface IEditorBuilderWrapper : IDependency { + dynamic BuildEditor(IContent content); + dynamic UpdateEditor(IContent content, IUpdateModel updateModel); + } +} diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml index e35d358db..2d5693802 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Item/Create.cshtml @@ -1,7 +1,13 @@ @using Orchard.ContentManagement @using Orchard.Utility.Extensions @using Orchard.ContentManagement.Aspects; +@using Orchard.ContentManagement.MetaData; +@using Orchard.ContentManagement.MetaData.Models; +@using Orchard.Core.Contents.Settings; + @{ + IContentDefinitionManager _contentDefinitionManager = WorkContext.Resolve(); + ContentItem customForm = Model.ContentItem; string returnUrl = Model.ReturnUrl; var metadata = customForm.ContentManager.GetItemMetadata(customForm); @@ -14,6 +20,18 @@ var submitButtonText = String.IsNullOrEmpty(Model.ContentItem.CustomFormPart.SubmitButtonText) ? T("Submit").Text : Model.ContentItem.CustomFormPart.SubmitButtonText; var publishButtonText = String.IsNullOrEmpty(Model.ContentItem.CustomFormPart.PublishButtonText) ? T("Publish").Text : Model.ContentItem.CustomFormPart.PublishButtonText; + + var showPublishButton = Model.ContentItem.CustomFormPart.SavePublishContentItem; + // Read type definition to check if content is draftable + var typeDefinition = _contentDefinitionManager + .ListTypeDefinitions() + .Where(x => String.Equals(x.Name, Model.ContentItem.CustomFormPart.ContentType, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + if (typeDefinition != null) { + // Publish button has to be visible only if content is draftable AND is set to show the publish button + showPublishButton = showPublishButton && + typeDefinition.Settings.GetModel().Draftable; + } } @Display(New.Parts_Title().Title(displayText)) @@ -29,8 +47,22 @@ @if (Model.ContentItem.CustomFormPart.SavePublishContentItem == false || Model.ContentItem.CustomFormPart.SaveContentItem == true) { } - @if (Model.ContentItem.CustomFormPart.SavePublishContentItem == true) { + @if (showPublishButton) { } + + +
+ @if (Model.Actions != null) { +
+ @Display(Model.Actions) +
+ } + @if (Model.Sidebar != null) { +
+ @Display(Model.Sidebar) +
+ } +
} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml index fe0d2109a..ee78a66f9 100644 --- a/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml +++ b/src/Orchard.Web/Modules/Orchard.CustomForms/Views/Parts.CustomForm.Wrapper.cshtml @@ -1,4 +1,10 @@ -@{ +@using Orchard.ContentManagement.MetaData; +@using Orchard.ContentManagement.MetaData.Models; +@using Orchard.Core.Contents.Settings; + +@{ + IContentDefinitionManager _contentDefinitionManager = WorkContext.Resolve(); + dynamic editor = Model.Editor; if (TempData.ContainsKey("CustomFormWidget.InvalidCustomFormState")) { @@ -7,18 +13,34 @@ // remove default Save/Publish buttons editor.Zones["Sidebar"].Items.Clear(); + + var showPublishButton = Model.ContentItem.CustomFormPart.SavePublishContentItem; + + // Read type definition to check if content is draftable + var typeDefinition = _contentDefinitionManager + .ListTypeDefinitions() + .Where(x => String.Equals(x.Name, Model.ContentItem.CustomFormPart.ContentType, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + if (typeDefinition != null) { + // Publish button has to be visible only if content is draftable AND is set to show the publish button + showPublishButton = showPublishButton && + typeDefinition.Settings.GetModel().Draftable; + } } @using (Html.BeginFormAntiForgeryPost(Url.Action("Create", "Item", new { area = "Orchard.CustomForms", id = Model.ContentItem.Id }))) { - @Html.ValidationSummary() +@Html.ValidationSummary() // Model is a Shape, calling Display() so that it is rendered using the most specific template for its Shape type - @Display(editor) - @Html.Hidden("returnUrl", Request.RawUrl, new { id = string.Empty }); - @Html.Hidden("contentId", !string.IsNullOrWhiteSpace(Request.QueryString["contentId"]) ? Request.QueryString["contentId"] : "0", new { id = string.Empty }); -
- - @if (Model.ContentItem.CustomFormPart.SavePublishContentItem == true) { - - } -
-} +@Display(editor) + +@Html.Hidden("returnUrl", Request.RawUrl, new { id = string.Empty }); +@Html.Hidden("contentId", !string.IsNullOrWhiteSpace(Request.QueryString["contentId"]) ? Request.QueryString["contentId"] : "0", new { id = string.Empty }); + +
+ + + @if (showPublishButton) { + + } +
+} \ 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 9c413a325..cbf33106f 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Data; using System.Linq; using Orchard.ContentManagement.MetaData; @@ -8,7 +7,6 @@ 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; @@ -17,22 +15,23 @@ 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(); + /// + /// When upgrading from "1.10.x" branch code committed after 1.10.3 to "dev" branch code or 1.11, merge + /// conflicts between "1.10.x" and "dev" caused by running the same migration steps in a different order need to + /// be resolved by instructing this migration to decide which steps need to be executed. If you're upgrading + /// under these conditions and your pre-upgrade migration version is 6, use HostComponents.config to override + /// this property to true. + /// + public bool IsUpgradingFromOrchard_1_10_x_Version_6 { get; set; } public Migrations( IRepository memberBindingRepository, IRepository layoutRepository, - IRepository propertyRecordRepository, - ISessionFactoryHolder sessionFactoryHolder, - ShellSettings shellSettings) { + IRepository propertyRecordRepository) { _memberBindingRepository = memberBindingRepository; _layoutRepository = layoutRepository; _propertyRecordRepository = propertyRecordRepository; - _sessionFactoryHolder = sessionFactoryHolder; - _shellSettings = shellSettings; T = NullLocalizer.Instance; } @@ -48,6 +47,7 @@ namespace Orchard.Projections { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value", c => c.WithLength(4000)) + .Column("LatestValue", c => c.WithLength(4000)) .Column("FieldIndexPartRecord_Id") ); @@ -56,6 +56,7 @@ namespace Orchard.Projections { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value") + .Column("LatestValue") .Column("FieldIndexPartRecord_Id") ); @@ -64,6 +65,7 @@ namespace Orchard.Projections { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value") + .Column("LatestValue") .Column("FieldIndexPartRecord_Id") ); @@ -72,9 +74,28 @@ namespace Orchard.Projections { .Column("Id", c => c.PrimaryKey().Identity()) .Column("PropertyName") .Column("Value") + .Column("LatestValue") .Column("FieldIndexPartRecord_Id") ); + //Adds indexes for better performances in queries + SchemaBuilder.AlterTable("StringFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_StringFieldIndexRecord", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_IntegerFieldIndexRecord", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_DoubleFieldIndexRecord", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_DecimalFieldIndexRecords", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.CreateTable("FieldIndexPartRecord", table => table.ContentPartRecord()); // Query @@ -89,6 +110,7 @@ namespace Orchard.Projections { SchemaBuilder.CreateTable("QueryPartRecord", table => table .ContentPartRecord() + .Column("VersionScope", c => c.WithLength(15)) ); SchemaBuilder.CreateTable("FilterGroupRecord", @@ -127,6 +149,7 @@ namespace Orchard.Projections { .Column("Description", c => c.WithLength(255)) .Column("State", c => c.Unlimited()) .Column("DisplayType", c => c.WithLength(64)) + .Column("GUIdentifier", column => column.WithLength(68)) .Column("Display") .Column("QueryPartRecord_id") .Column("GroupProperty_id") @@ -162,6 +185,7 @@ namespace Orchard.Projections { .Column("HideEmpty") .Column("RewriteOutput") + .Column("RewriteOutputCondition", c => c.Unlimited()) .Column("RewriteText", c => c.Unlimited()) .Column("StripHtmlTags") .Column("TrimLength") @@ -260,19 +284,6 @@ namespace Orchard.Projections { Description = T("The text from the Body part").Text }); - SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table - .CreateIndex("IDX_Orchard_Projections_StringFieldIndexRecord", "FieldIndexPartRecord_Id") - ); - SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table - .CreateIndex("IDX_Orchard_Projections_IntegerFieldIndexRecord", "FieldIndexPartRecord_Id") - ); - SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table - .CreateIndex("IDX_Orchard_Projections_DoubleFieldIndexRecord", "FieldIndexPartRecord_Id") - ); - SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table - .CreateIndex("IDX_Orchard_Projections_DecimalFieldIndexRecords", "FieldIndexPartRecord_Id") - ); - SchemaBuilder.CreateTable("NavigationQueryPartRecord", table => table.ContentPartRecord() .Column("Items") @@ -291,7 +302,7 @@ namespace Orchard.Projections { .WithIdentity() ); - return 4; + return 7; } public int UpdateFrom1() { @@ -314,7 +325,7 @@ namespace Orchard.Projections { ContentDefinitionManager.AlterTypeDefinition("ProjectionPage", cfg => cfg.Listable()); - return 3; + return 2; } public int UpdateFrom2() { @@ -336,29 +347,34 @@ namespace Orchard.Projections { public int UpdateFrom4() { SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table - .AddColumn("LatestValue", c => c.WithLength(4000))); + .AddColumn("LatestValue", c => c.WithLength(4000))); SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table - .AddColumn("LatestValue")); + .AddColumn("LatestValue")); SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table - .AddColumn("LatestValue")); + .AddColumn("LatestValue")); SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table - .AddColumn("LatestValue")); + .AddColumn("LatestValue")); //Adds indexes for better performances in queries - SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" })); - SchemaBuilder.AlterTable("StringFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" })); - - SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" })); - SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" })); - - SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" })); - SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" })); - - SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table.CreateIndex("IX_PropertyName", new string[] { "PropertyName" })); - SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => table.CreateIndex("IX_FieldIndexPartRecord_Id", new string[] { "FieldIndexPartRecord_Id" })); + SchemaBuilder.AlterTable("StringFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_StringFieldIndexRecord", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.AlterTable("IntegerFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_IntegerFieldIndexRecord", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.AlterTable("DoubleFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_DoubleFieldIndexRecord", "FieldIndexPartRecord_Id"); + }); + SchemaBuilder.AlterTable("DecimalFieldIndexRecord", table => { + table.CreateIndex("IDX_Orchard_Projections_PropertyName", "PropertyName"); + table.CreateIndex("IDX_Orchard_Projections_DecimalFieldIndexRecords", "FieldIndexPartRecord_Id"); + }); SchemaBuilder.AlterTable("QueryPartRecord", table => table .AddColumn("VersionScope", c => c.WithLength(15))); @@ -366,30 +382,32 @@ namespace Orchard.Projections { return 5; } - // 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(); + if (IsUpgradingFromOrchard_1_10_x_Version_6) { + MigratePropertyRecordToRewriteOutputCondition(); + } + else { + // This change was originally UpdateFrom5 on 1.10.x and UpdateFrom6 on dev. + SchemaBuilder.AlterTable("LayoutRecord", table => + table.AddColumn("GUIdentifier", column => column.WithLength(68))); - // When upgrading from version 6 of 1.10.x, this column isn't created yet, so we need to run this step - // "again". - MigratePropertyRecordToRewriteOutputCondition(); + var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList(); + foreach (var layout in layoutRecords) { + layout.GUIdentifier = Guid.NewGuid().ToString(); + } + } return 7; } - // This change was originally UpdateFrom5 on dev (but didn't exist on 1.10.x). + // This change was originally in 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()) ); @@ -399,46 +417,6 @@ namespace Orchard.Projections { // 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 - - ColumnAdded("PropertyRecord", "RewriteOutputCondition"); } - - // 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(); - } - - 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}"); } }