From 3ddb51706813ecdfb0a9c44d9be7d0b69da22ed1 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Tue, 2 Jul 2024 16:00:00 -0700 Subject: [PATCH 01/45] save incomplete work --- OrchardCore.sln | 7 ++ .../Controllers/AdminController.cs | 93 ++++++++++------ .../OrchardCore.Queries.csproj | 2 +- .../Services/QueryManager.cs | 103 +++++++++++------- .../Sql/Drivers/SqlQueryDisplayDriver.cs | 49 ++++++--- .../Sql/GraphQL/SqlQueryFieldTypeProvider.cs | 88 +++++++++------ .../Sql/Models/SqlQueryMetadata.cs | 8 ++ .../OrchardCore.Queries/Sql/SqlQuery.cs | 8 +- .../OrchardCore.Queries/Sql/SqlQuerySource.cs | 32 +++--- .../OrchardCore.Queries/Sql/Startup.cs | 2 + .../GraphQL/ElasticQueryFieldTypeProvider.cs | 61 ++++++----- .../OrchardCore.Search.Elasticsearch.csproj | 4 +- .../GraphQL/LuceneQueryFieldTypeProvider.cs | 55 ++++++---- .../OrchardCore.Search.Lucene.csproj | 1 + .../IQueryManager.cs | 54 --------- .../OrchardCore.Queries.Abstractions.csproj | 8 +- .../IQuerySource.cs | 0 .../Indexes/QueryIndex.cs | 24 ++++ .../OrchardCore.Queries.Core.csproj | 27 +++++ .../Query.cs | 18 ++- .../IElasticQueryService.cs | 5 +- 21 files changed, 400 insertions(+), 249 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs delete mode 100644 src/OrchardCore/OrchardCore.Queries.Abstractions/IQueryManager.cs rename src/OrchardCore/{OrchardCore.Queries.Abstractions => OrchardCore.Queries.Core}/IQuerySource.cs (100%) create mode 100644 src/OrchardCore/OrchardCore.Queries.Core/Indexes/QueryIndex.cs create mode 100644 src/OrchardCore/OrchardCore.Queries.Core/OrchardCore.Queries.Core.csproj rename src/OrchardCore/{OrchardCore.Queries.Abstractions => OrchardCore.Queries.Core}/Query.cs (74%) diff --git a/OrchardCore.sln b/OrchardCore.sln index b1ba74e51d1..95a51c8d18b 100644 --- a/OrchardCore.sln +++ b/OrchardCore.sln @@ -523,6 +523,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Email.Smtp", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrchardCore.Rules.Core", "src\OrchardCore\OrchardCore.Rules.Core\OrchardCore.Rules.Core.csproj", "{4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrchardCore.Queries.Core", "src\OrchardCore\OrchardCore.Queries.Core\OrchardCore.Queries.Core.csproj", "{61B358F2-702C-40AA-9DF7-7121248FE6DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1383,6 +1385,10 @@ Global {4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7}.Release|Any CPU.Build.0 = Release|Any CPU + {61B358F2-702C-40AA-9DF7-7121248FE6DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61B358F2-702C-40AA-9DF7-7121248FE6DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61B358F2-702C-40AA-9DF7-7121248FE6DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61B358F2-702C-40AA-9DF7-7121248FE6DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1622,6 +1628,7 @@ Global {C35AB37B-5A09-4896-BEEE-B126B7E7018A} = {A066395F-6F73-45DC-B5A6-B4E306110DCE} {E8A1097D-A65A-4B17-A3A2-F50D79552732} = {A066395F-6F73-45DC-B5A6-B4E306110DCE} {4BAA08A2-878C-4B96-86BF-5B3DB2B6C2C7} = {F23AC6C2-DE44-4699-999D-3C478EF3D691} + {61B358F2-702C-40AA-9DF7-7121248FE6DE} = {F23AC6C2-DE44-4699-999D-3C478EF3D691} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {46A1D25A-78D1-4476-9CBF-25B75E296341} diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs index bb5f1dbaa2d..aa92c7763af 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs @@ -14,8 +14,11 @@ using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Notify; using OrchardCore.Navigation; +using OrchardCore.Queries.Indexes; using OrchardCore.Queries.ViewModels; using OrchardCore.Routing; +using YesSql; +using YesSql.Services; namespace OrchardCore.Queries.Controllers { @@ -27,8 +30,8 @@ public class AdminController : Controller private readonly IAuthorizationService _authorizationService; private readonly PagerOptions _pagerOptions; private readonly INotifier _notifier; - private readonly IQueryManager _queryManager; private readonly IEnumerable _querySources; + private readonly ISession _session; private readonly IDisplayManager _displayManager; private readonly IUpdateModelAccessor _updateModelAccessor; private readonly IShapeFactory _shapeFactory; @@ -37,6 +40,7 @@ public class AdminController : Controller protected readonly IHtmlLocalizer H; public AdminController( + ISession session, IDisplayManager displayManager, IAuthorizationService authorizationService, IOptions pagerOptions, @@ -44,14 +48,13 @@ public AdminController( IStringLocalizer stringLocalizer, IHtmlLocalizer htmlLocalizer, INotifier notifier, - IQueryManager queryManager, IEnumerable querySources, IUpdateModelAccessor updateModelAccessor) { + _session = session; _displayManager = displayManager; _authorizationService = authorizationService; _pagerOptions = pagerOptions.Value; - _queryManager = queryManager; _querySources = querySources; _updateModelAccessor = updateModelAccessor; _shapeFactory = shapeFactory; @@ -69,18 +72,7 @@ public async Task Index(ContentOptions options, PagerParameters p var pager = new Pager(pagerParameters, _pagerOptions.GetPageSize()); - var queries = await _queryManager.ListQueriesAsync(); - queries = queries.OrderBy(x => x.Name); - - if (!string.IsNullOrWhiteSpace(options.Search)) - { - queries = queries.Where(q => q.Name.Contains(options.Search, StringComparison.OrdinalIgnoreCase)); - } - - var results = queries - .Skip(pager.GetStartIndex()) - .Take(pager.PageSize) - .ToList(); + var query = _session.Query(); // Maintain previous route data when generating page links. var routeData = new RouteData(); @@ -88,22 +80,29 @@ public async Task Index(ContentOptions options, PagerParameters p if (!string.IsNullOrEmpty(options.Search)) { routeData.Values.TryAdd(_optionsSearch, options.Search); + + query = query.Where(x => x.Name != null && x.Name.Contains(options.Search)); } + var skip = (pager.Page - 1) * pager.PageSize; + + var count = await query.CountAsync(); + var queries = await query.Skip(skip).Take(pager.PageSize).ListAsync(); + var model = new QueriesIndexViewModel { Queries = [], Options = options, - Pager = await _shapeFactory.PagerAsync(pager, queries.Count(), routeData), + Pager = await _shapeFactory.PagerAsync(pager, count, routeData), QuerySourceNames = _querySources.Select(x => x.Name).ToList() }; - foreach (var query in results) + foreach (var qry in queries) { model.Queries.Add(new QueryEntry { - Query = query, - Shape = await _displayManager.BuildDisplayAsync(query, _updateModelAccessor.ModelUpdater, "SummaryAdmin") + Query = qry, + Shape = await _displayManager.BuildDisplayAsync(qry, _updateModelAccessor.ModelUpdater, "SummaryAdmin") }); } @@ -115,7 +114,8 @@ public async Task Index(ContentOptions options, PagerParameters p return View(model); } - [HttpPost, ActionName(nameof(Index))] + [HttpPost] + [ActionName(nameof(Index))] [FormValueRequired("submit.Filter")] public ActionResult IndexFilterPOST(QueriesIndexViewModel model) => RedirectToAction(nameof(Index), new RouteValueDictionary @@ -165,13 +165,16 @@ public async Task CreatePost(QueriesCreateViewModel model) if (ModelState.IsValid) { - await _queryManager.SaveQueryAsync(query.Name, query); + await DeleteQueryInternalAsync(query.Name, false); + await _session.SaveAsync(query); + await _session.SaveChangesAsync(); await _notifier.SuccessAsync(H["Query created successfully."]); + return RedirectToAction(nameof(Index)); } - // If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. model.Editor = editor; return View(model); @@ -184,7 +187,7 @@ public async Task Edit(string id) return Forbid(); } - var query = await _queryManager.GetQueryAsync(id); + var query = await _session.Query(x => x.Name == id).FirstOrDefaultAsync(); if (query == null) { @@ -210,7 +213,7 @@ public async Task EditPost(QueriesEditViewModel model) return Forbid(); } - var query = await _queryManager.LoadQueryAsync(model.Name); + var query = await _session.Query(x => x.Name == model.Name).FirstOrDefaultAsync(); if (query == null) { @@ -221,15 +224,16 @@ public async Task EditPost(QueriesEditViewModel model) if (ModelState.IsValid) { - await _queryManager.SaveQueryAsync(model.Name, query); - + await _session.SaveAsync(query); + await _session.SaveChangesAsync(); await _notifier.SuccessAsync(H["Query updated successfully."]); + return RedirectToAction(nameof(Index)); } model.Editor = editor; - // If we got this far, something failed, redisplay form + // If we got this far, something failed, redisplay form. return View(model); } @@ -241,15 +245,11 @@ public async Task Delete(string id) return Forbid(); } - var query = await _queryManager.LoadQueryAsync(id); - - if (query == null) + if (!await DeleteQueryInternalAsync(id, true)) { return NotFound(); } - await _queryManager.DeleteQueryAsync(id); - await _notifier.SuccessAsync(H["Query deleted successfully."]); return RedirectToAction(nameof(Index)); @@ -266,25 +266,46 @@ public async Task IndexPost(ContentOptions options, IEnumerable 0) { - var queriesList = await _queryManager.ListQueriesAsync(); - var checkedContentItems = queriesList.Where(x => itemIds.Contains(x.Name)); switch (options.BulkAction) { case ContentsBulkAction.None: break; case ContentsBulkAction.Remove: + var checkedContentItems = await _session.Query(x => x.Name != null && x.Name.IsIn(itemIds)).ListAsync(); + foreach (var item in checkedContentItems) { - await _queryManager.DeleteQueryAsync(item.Name); + _session.Delete(item); } + + await _session.SaveChangesAsync(); await _notifier.SuccessAsync(H["Queries successfully removed."]); break; default: - throw new ArgumentOutOfRangeException(options.BulkAction.ToString(), "Invalid bulk action."); + return BadRequest(); } } return RedirectToAction(nameof(Index)); } + + private async Task DeleteQueryInternalAsync(string name, bool commit) + { + var queries = await _session.Query(x => x.Name == name).ListAsync(); + + var hasQueries = queries.Any(); + + foreach (var query in queries) + { + _session.Delete(query); + } + + if (commit && hasQueries) + { + await _session.SaveChangesAsync(); + } + + return hasQueries; + } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/OrchardCore.Queries.csproj b/src/OrchardCore.Modules/OrchardCore.Queries/OrchardCore.Queries.csproj index 56031665529..e803bfa1c80 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/OrchardCore.Queries.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Queries/OrchardCore.Queries.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs index 0f475ca98c6..6c9678a22b4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs @@ -2,76 +2,88 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using OrchardCore.Documents; +using OrchardCore.Queries.Indexes; +using YesSql; namespace OrchardCore.Queries.Services { public class QueryManager : IQueryManager { - private readonly IDocumentManager _documentManager; private readonly IEnumerable _querySources; + private readonly ISession _session; - public QueryManager(IDocumentManager documentManager, IEnumerable querySources) + public QueryManager( + IEnumerable querySources, + ISession session) { - _documentManager = documentManager; _querySources = querySources; + _session = session; } - public async Task GetIdentifierAsync() => (await GetDocumentAsync()).Identifier; - public async Task DeleteQueryAsync(string name) { - var existing = await LoadDocumentAsync(); - existing.Queries.Remove(name); - await _documentManager.UpdateAsync(existing); + ArgumentException.ThrowIfNullOrEmpty(name); + + await DeleteQueryInternalAsync(name, true); } - public async Task LoadQueryAsync(string name) + public async Task GetQueryAsync(string name) { - var document = await LoadDocumentAsync(); + ArgumentException.ThrowIfNullOrEmpty(name); - if (document.Queries.TryGetValue(name, out var query)) - { - return query; - } + var query = await _session.Query(x => x.Name == name).FirstOrDefaultAsync(); - return null; + return query; } - public async Task GetQueryAsync(string name) + public async Task ListQueriesAsync(Func predicate = null, int? page = null, int? pageSize = null) { - var document = await GetDocumentAsync(); + var query = _session.Query(); - if (document.Queries.TryGetValue(name, out var query)) + if (predicate != null) { - return query; + query = query.Where(q => predicate(q)); } - return null; - } + query = query + .OrderBy(x => x.Name) + .ThenBy(x => x.Id); - public async Task> ListQueriesAsync() - { - return (await GetDocumentAsync()).Queries.Values.ToList(); + if (page == null || page < 0) + { + page = 1; + } + + var count = await query.CountAsync(); + + if (pageSize > 0) + { + return new QueryPageResult() + { + Count = count, + Queries = await query.Take(pageSize.Value) + .Skip((page.Value - 1) * pageSize.Value) + .ListAsync(), + }; + } + + return new QueryPageResult() + { + Count = count, + Queries = await query.ListAsync(), + }; } public async Task SaveQueryAsync(string name, Query query) { - var existing = await LoadDocumentAsync(); - existing.Queries.Remove(name); - existing.Queries[query.Name] = query; - await _documentManager.UpdateAsync(existing); - } + ArgumentException.ThrowIfNullOrEmpty(name); - /// - /// Loads the queries document from the store for updating and that should not be cached. - /// - public Task LoadDocumentAsync() => _documentManager.GetOrCreateMutableAsync(); + ArgumentNullException.ThrowIfNull(query); - /// - /// Gets the queries document from the cache for sharing and that should not be updated. - /// - public Task GetDocumentAsync() => _documentManager.GetOrCreateImmutableAsync(); + await DeleteQueryInternalAsync(name, false); + + await _session.SaveAsync(query); + } public Task ExecuteQueryAsync(Query query, IDictionary parameters) { @@ -80,5 +92,20 @@ public Task ExecuteQueryAsync(Query query, IDictionary(x => x.Name == name).ListAsync(); + + foreach (var query in queries) + { + _session.Delete(query); + } + + if (commit && queries.Any()) + { + await _session.SaveChangesAsync(); + } + } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs index 58f1605e8e3..51ee5433304 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs @@ -3,11 +3,13 @@ using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Views; +using OrchardCore.Entities; +using OrchardCore.Queries.Sql.Models; using OrchardCore.Queries.Sql.ViewModels; namespace OrchardCore.Queries.Sql.Drivers { - public class SqlQueryDisplayDriver : DisplayDriver + public class SqlQueryDisplayDriver : DisplayDriver { protected readonly IStringLocalizer S; @@ -16,8 +18,13 @@ public SqlQueryDisplayDriver(IStringLocalizer stringLocal S = stringLocalizer; } - public override IDisplayResult Display(SqlQuery query, IUpdateModel updater) + public override IDisplayResult Display(Query query, IUpdateModel updater) { + if (query.Source != SqlQuerySource.SourceName) + { + return null; + } + return Combine( Dynamic("SqlQuery_SummaryAdmin", model => { @@ -30,35 +37,49 @@ public override IDisplayResult Display(SqlQuery query, IUpdateModel updater) ); } - public override IDisplayResult Edit(SqlQuery query, IUpdateModel updater) + public override IDisplayResult Edit(Query query, IUpdateModel updater) { + if (query.Source != SqlQuerySource.SourceName) + { + return null; + } + return Initialize("SqlQuery_Edit", model => { - model.Query = query.Template; - model.ReturnDocuments = query.ReturnDocuments; + var metadata = query.As(); + model.Query = metadata.Template; + model.ReturnDocuments = metadata.ReturnDocuments; // Extract query from the query string if we come from the main query editor. - if (string.IsNullOrEmpty(query.Template)) + if (string.IsNullOrEmpty(metadata.Template)) { - updater.TryUpdateModelAsync(model, "", m => m.Query); + updater.TryUpdateModelAsync(model, string.Empty, m => m.Query); } }).Location("Content:5"); } - public override async Task UpdateAsync(SqlQuery model, IUpdateModel updater) + public override async Task UpdateAsync(Query query, IUpdateModel updater) { + if (query.Source != SqlQuerySource.SourceName) + { + return null; + } + var viewModel = new SqlQueryViewModel(); await updater.TryUpdateModelAsync(viewModel, Prefix, m => m.Query, m => m.ReturnDocuments); - model.Template = viewModel.Query; - model.ReturnDocuments = viewModel.ReturnDocuments; - - if (string.IsNullOrWhiteSpace(model.Template)) + if (string.IsNullOrWhiteSpace(viewModel.Query)) { - updater.ModelState.AddModelError(nameof(model.Template), S["The query field is required"]); + updater.ModelState.AddModelError(nameof(viewModel.Query), S["The query field is required"]); } - return Edit(model, updater); + query.Put(new SqlQueryMetadata() + { + Template = viewModel.Query, + ReturnDocuments = viewModel.ReturnDocuments, + }); + + return Edit(query, updater); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs index 811c97aba68..afb99cc75de 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs @@ -10,42 +10,47 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using OrchardCore.Apis.GraphQL; using OrchardCore.Apis.GraphQL.Resolvers; using OrchardCore.ContentManagement.GraphQL.Queries; +using OrchardCore.Entities; +using OrchardCore.Queries.Indexes; +using OrchardCore.Queries.Sql.Models; +using YesSql; namespace OrchardCore.Queries.Sql.GraphQL.Queries { /// - /// This implementation of registers + /// This implementation of registers /// all SQL Queries as GraphQL queries. /// - public class SqlQueryFieldTypeProvider : ISchemaBuilder + public class SqlQueryFieldTypeProvider : Apis.GraphQL.ISchemaBuilder { private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; - public SqlQueryFieldTypeProvider(IHttpContextAccessor httpContextAccessor, ILogger logger) + public SqlQueryFieldTypeProvider( + IHttpContextAccessor httpContextAccessor, + ILogger logger) { _httpContextAccessor = httpContextAccessor; _logger = logger; } + public Task GetIdentifierAsync() - { - var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); - return queryManager.GetIdentifierAsync(); - } + => Task.FromResult(string.Empty); public async Task BuildAsync(ISchema schema) { - var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); + var session = _httpContextAccessor.HttpContext.RequestServices.GetService(); - var queries = await queryManager.ListQueriesAsync(); + var queries = await session.Query().ListAsync(); - foreach (var query in queries.OfType()) + foreach (var query in queries) { if (string.IsNullOrWhiteSpace(query.Schema)) + { continue; + } var name = query.Name; @@ -60,9 +65,11 @@ public async Task BuildAsync(ISchema schema) var type = querySchema["type"].ToString(); FieldType fieldType; + var sqlQueryMetadata = query.As(); + var fieldTypeName = querySchema["fieldTypeName"]?.ToString() ?? query.Name; - if (query.ReturnDocuments && type.StartsWith("ContentItem/", StringComparison.OrdinalIgnoreCase)) + if (sqlQueryMetadata.ReturnDocuments && type.StartsWith("ContentItem/", StringComparison.OrdinalIgnoreCase)) { var contentType = type.Remove(0, 12); fieldType = BuildContentTypeFieldType(schema, contentType, query, fieldTypeName); @@ -84,7 +91,7 @@ public async Task BuildAsync(ISchema schema) } } - private static FieldType BuildSchemaBasedFieldType(SqlQuery query, JsonNode querySchema, string fieldTypeName) + private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySchema, string fieldTypeName) { var properties = querySchema["properties"]?.AsObject(); if (properties == null) @@ -92,7 +99,7 @@ private static FieldType BuildSchemaBasedFieldType(SqlQuery query, JsonNode quer return null; } - var typetype = new ObjectGraphType + var typeType = new ObjectGraphType { Name = fieldTypeName }; @@ -118,7 +125,7 @@ private static FieldType BuildSchemaBasedFieldType(SqlQuery query, JsonNode quer }), }; field.Metadata.Add("Name", name); - typetype.AddField(field); + typeType.AddField(field); } else if (type == "integer") { @@ -133,9 +140,9 @@ private static FieldType BuildSchemaBasedFieldType(SqlQuery query, JsonNode quer return source[context.FieldDefinition.Metadata["Name"].ToString()].ToObject(); }), }; - + field.Metadata.Add("Name", name); - typetype.AddField(field); + typeType.AddField(field); } } @@ -147,23 +154,28 @@ private static FieldType BuildSchemaBasedFieldType(SqlQuery query, JsonNode quer Name = fieldTypeName, Description = "Represents the " + query.Source + " Query : " + query.Name, - ResolvedType = new ListGraphType(typetype), + ResolvedType = new ListGraphType(typeType), Resolver = new LockedAsyncFieldResolver(ResolveAsync), Type = typeof(ListGraphType>) }; async ValueTask ResolveAsync(IResolveFieldContext context) { - var queryManager = context.RequestServices.GetService(); - var iquery = await queryManager.GetQueryAsync(query.Name); + var session = context.RequestServices.GetService(); + var querySources = context.RequestServices.GetServices(); + + var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); var parameters = context.GetArgument("parameters"); - var queryParameters = parameters != null ? - JConvert.DeserializeObject>(parameters) - : []; + var queryParameters = parameters != null ? + JConvert.DeserializeObject>(parameters) + : []; + + var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) + ?? throw new ArgumentException("Query source not found: " + query.Source); - var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters); + var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; } @@ -171,10 +183,10 @@ async ValueTask ResolveAsync(IResolveFieldContext context) return fieldType; } - private static FieldType BuildContentTypeFieldType(ISchema schema, string contentType, SqlQuery query, string fieldTypeName) + private static FieldType BuildContentTypeFieldType(ISchema schema, string contentType, Query query, string fieldTypeName) { - var typetype = schema.Query.Fields.OfType().FirstOrDefault(x => x.Name == contentType); - if (typetype == null) + var typeType = schema.Query.Fields.OfType().FirstOrDefault(x => x.Name == contentType); + if (typeType == null) { return null; } @@ -187,23 +199,29 @@ private static FieldType BuildContentTypeFieldType(ISchema schema, string conten Name = fieldTypeName, Description = "Represents the " + query.Source + " Query : " + query.Name, - ResolvedType = typetype.ResolvedType, + ResolvedType = typeType.ResolvedType, Resolver = new LockedAsyncFieldResolver(ResolveAsync), - Type = typetype.Type + Type = typeType.Type }; async ValueTask ResolveAsync(IResolveFieldContext context) { - var queryManager = context.RequestServices.GetService(); - var iquery = await queryManager.GetQueryAsync(query.Name); + var session = context.RequestServices.GetService(); + var querySources = context.RequestServices.GetServices(); + + var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); var parameters = context.GetArgument("parameters"); - var queryParameters = parameters != null ? - JConvert.DeserializeObject>(parameters) - : []; + var queryParameters = parameters != null ? + JConvert.DeserializeObject>(parameters) + : []; + + var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) + ?? throw new ArgumentException("Query source not found: " + query.Source); + + var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); - var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters); return result.Items; } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs new file mode 100644 index 00000000000..c9c8a87fef4 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs @@ -0,0 +1,8 @@ +namespace OrchardCore.Queries.Sql.Models; + +public class SqlQueryMetadata +{ + public string Template { get; set; } + + public bool ReturnDocuments { get; set; } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs index 533aa81a07d..3569543b2c1 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs @@ -1,15 +1,21 @@ +using System; using OrchardCore.ContentManagement; namespace OrchardCore.Queries.Sql { + [Obsolete("This class will be removed in future release. Instead use Query.")] public class SqlQuery : Query { - public SqlQuery() : base("Sql") + public SqlQuery() : base(SqlQuerySource.SourceName) { } + [Obsolete("Use .As() instead to get this property value.")] public string Template { get; set; } + + [Obsolete("Use .As() instead to get this property value.")] public bool ReturnDocuments { get; set; } + public override bool ResultsOfType() => ReturnDocuments ? typeof(T) == typeof(ContentItem) : base.ResultsOfType(); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs index c26dd1254e9..6d41100f954 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; @@ -11,14 +10,18 @@ using OrchardCore.ContentManagement; using OrchardCore.ContentManagement.Records; using OrchardCore.Data; +using OrchardCore.Entities; using OrchardCore.Json; using OrchardCore.Liquid; +using OrchardCore.Queries.Sql.Models; using YesSql; namespace OrchardCore.Queries.Sql { public class SqlQuerySource : IQuerySource { + public const string SourceName = "Sql"; + private readonly ILiquidTemplateManager _liquidTemplateManager; private readonly IDbConnectionAccessor _dbConnectionAccessor; private readonly ISession _session; @@ -39,26 +42,29 @@ public SqlQuerySource( _templateOptions = templateOptions.Value; } - public string Name => "Sql"; + public string Name + => SourceName; public Query Create() - { - return new SqlQuery(); - } + => new Query() + { + Source = SourceName, + }; public async Task ExecuteQueryAsync(Query query, IDictionary parameters) { - var sqlQuery = query as SqlQuery; + var sqlQueryMetadata = query.As(); + var sqlQueryResults = new SQLQueryResults(); - var tokenizedQuery = await _liquidTemplateManager.RenderStringAsync(sqlQuery.Template, NullEncoder.Default, + var tokenizedQuery = await _liquidTemplateManager.RenderStringAsync(sqlQueryMetadata.Template, NullEncoder.Default, parameters.Select(x => new KeyValuePair(x.Key, FluidValue.Create(x.Value, _templateOptions)))); var dialect = _session.Store.Configuration.SqlDialect; if (!SqlParser.TryParse(tokenizedQuery, _session.Store.Configuration.Schema, dialect, _session.Store.Configuration.TablePrefix, parameters, out var rawQuery, out var messages)) { - sqlQueryResults.Items = Array.Empty(); + sqlQueryResults.Items = []; return sqlQueryResults; } @@ -67,16 +73,14 @@ public async Task ExecuteQueryAsync(Query query, IDictionary documentIds; - using var transaction = await connection.BeginTransactionAsync(_session.Store.Configuration.IsolationLevel); var queryResult = await connection.QueryAsync(rawQuery, parameters, transaction); string column = null; - documentIds = queryResult.Select(row => + var documentIds = queryResult.Select(row => { var rowDictionary = (IDictionary)row; @@ -97,9 +101,9 @@ public async Task ExecuteQueryAsync(Query query, IDictionary(documentIds.ToArray()); + sqlQueryResults.Items = await _session.GetAsync(documentIds); return sqlQueryResults; } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Startup.cs index a69e2cb817a..504fa985c20 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Startup.cs @@ -22,7 +22,9 @@ public override void ConfigureServices(IServiceCollection services) services.AddScoped(); // Allows to serialize 'SqlQuery' from its base type. +#pragma warning disable CS0618 // Type or member is obsolete services.AddJsonDerivedTypeInfo(); +#pragma warning restore CS0618 // Type or member is obsolete } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs index 0cf49110b5f..73cd6381a28 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/GraphQL/ElasticQueryFieldTypeProvider.cs @@ -14,7 +14,9 @@ using OrchardCore.Apis.GraphQL.Resolvers; using OrchardCore.ContentManagement.GraphQL.Queries; using OrchardCore.Queries; +using OrchardCore.Queries.Indexes; using OrchardCore.Search.Elasticsearch.Core.Models; +using YesSql; namespace OrchardCore.Search.Elasticsearch.GraphQL.Queries { @@ -30,16 +32,13 @@ public ElasticQueryFieldTypeProvider(IHttpContextAccessor httpContextAccessor, I } public Task GetIdentifierAsync() - { - var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); - return queryManager.GetIdentifierAsync(); - } + => Task.FromResult(string.Empty); public async Task BuildAsync(ISchema schema) { - var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); + var session = _httpContextAccessor.HttpContext.RequestServices.GetService(); - var queries = await queryManager.ListQueriesAsync(); + var queries = await session.Query().ListAsync(); foreach (var query in queries.OfType()) { @@ -92,7 +91,7 @@ private static FieldType BuildSchemaBasedFieldType(ElasticQuery query, JsonNode return null; } - var typetype = new ObjectGraphType + var typeType = new ObjectGraphType { Name = fieldTypeName }; @@ -118,7 +117,7 @@ private static FieldType BuildSchemaBasedFieldType(ElasticQuery query, JsonNode }), }; field.Metadata.Add("Name", name); - typetype.AddField(field); + typeType.AddField(field); } else if (type == "integer") { @@ -135,7 +134,7 @@ private static FieldType BuildSchemaBasedFieldType(ElasticQuery query, JsonNode }; field.Metadata.Add("Name", name); - typetype.AddField(field); + typeType.AddField(field); } } @@ -147,23 +146,28 @@ private static FieldType BuildSchemaBasedFieldType(ElasticQuery query, JsonNode Name = fieldTypeName, Description = "Represents the " + query.Source + " Query : " + query.Name, - ResolvedType = new ListGraphType(typetype), + ResolvedType = new ListGraphType(typeType), Resolver = new LockedAsyncFieldResolver(ResolveAsync), Type = typeof(ListGraphType>) }; async ValueTask ResolveAsync(IResolveFieldContext context) { - var queryManager = context.RequestServices.GetService(); - var iquery = await queryManager.GetQueryAsync(query.Name); + var session = context.RequestServices.GetService(); + var querySources = context.RequestServices.GetServices(); + + var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); var parameters = context.GetArgument("parameters"); - var queryParameters = parameters != null ? - JConvert.DeserializeObject>(parameters) - : []; + var queryParameters = parameters != null ? + JConvert.DeserializeObject>(parameters) + : []; - var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters); + var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) + ?? throw new ArgumentException("Query source not found: " + query.Source); + + var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; } @@ -173,9 +177,9 @@ async ValueTask ResolveAsync(IResolveFieldContext context) private static FieldType BuildContentTypeFieldType(ISchema schema, string contentType, ElasticQuery query, string fieldTypeName) { - var typetype = schema.Query.Fields.OfType().FirstOrDefault(x => x.Name == contentType); + var typeType = schema.Query.Fields.OfType().FirstOrDefault(x => x.Name == contentType); - if (typetype == null) + if (typeType == null) { return null; } @@ -188,23 +192,28 @@ private static FieldType BuildContentTypeFieldType(ISchema schema, string conten Name = fieldTypeName, Description = "Represents the " + query.Source + " Query : " + query.Name, - ResolvedType = typetype.ResolvedType, + ResolvedType = typeType.ResolvedType, Resolver = new LockedAsyncFieldResolver(ResolveAsync), - Type = typetype.Type + Type = typeType.Type }; async ValueTask ResolveAsync(IResolveFieldContext context) { - var queryManager = context.RequestServices.GetService(); - var iquery = await queryManager.GetQueryAsync(query.Name); + var session = context.RequestServices.GetService(); + var querySources = context.RequestServices.GetServices(); + + var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); var parameters = context.GetArgument("parameters"); - var queryParameters = parameters != null ? - JConvert.DeserializeObject>(parameters) - : []; + var queryParameters = parameters != null ? + JConvert.DeserializeObject>(parameters) + : []; + + var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) + ?? throw new ArgumentException("Query source not found: " + query.Source); - var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters); + var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; } diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/OrchardCore.Search.Elasticsearch.csproj b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/OrchardCore.Search.Elasticsearch.csproj index ba3e2be38ae..15455199d01 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/OrchardCore.Search.Elasticsearch.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/OrchardCore.Search.Elasticsearch.csproj @@ -7,7 +7,8 @@ $(OCCMSDescription) - Creates Elasticsearch Search indexes to support search scenarios, introduces a preconfigured container-enabled content type. + Creates Elasticsearch Search indexes to support search scenarios, introduces a preconfigured container-enabled content type. + $(PackageTags) OrchardCoreCMS @@ -21,6 +22,7 @@ + diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs index def951a752c..1c666bd1de0 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/GraphQL/LuceneQueryFieldTypeProvider.cs @@ -13,7 +13,9 @@ using OrchardCore.Apis.GraphQL; using OrchardCore.Apis.GraphQL.Resolvers; using OrchardCore.ContentManagement.GraphQL.Queries; +using OrchardCore.Queries.Indexes; using OrchardCore.Search.Lucene; +using YesSql; namespace OrchardCore.Queries.Lucene.GraphQL.Queries { @@ -29,21 +31,20 @@ public LuceneQueryFieldTypeProvider(IHttpContextAccessor httpContextAccessor, IL } public Task GetIdentifierAsync() - { - var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); - return queryManager.GetIdentifierAsync(); - } + => Task.FromResult(string.Empty); public async Task BuildAsync(ISchema schema) { - var queryManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); + var session = _httpContextAccessor.HttpContext.RequestServices.GetService(); - var queries = await queryManager.ListQueriesAsync(); + var queries = await session.Query().ListAsync(); foreach (var query in queries.OfType()) { if (string.IsNullOrWhiteSpace(query.Schema)) + { continue; + } var name = query.Name; @@ -53,8 +54,10 @@ public async Task BuildAsync(ISchema schema) if (!querySchema.ContainsKey("type")) { _logger.LogError("The Query '{Name}' schema is invalid, the 'type' property was not found.", name); + continue; } + var type = querySchema["type"].ToString(); FieldType fieldType; @@ -85,13 +88,13 @@ public async Task BuildAsync(ISchema schema) private static FieldType BuildSchemaBasedFieldType(LuceneQuery query, JsonNode querySchema, string fieldTypeName) { - var properties = querySchema["properties"]?.AsObject(); + var properties = querySchema["properties"]?.AsObject(); if (properties == null) { return null; } - var typetype = new ObjectGraphType + var typeType = new ObjectGraphType { Name = fieldTypeName }; @@ -117,7 +120,7 @@ private static FieldType BuildSchemaBasedFieldType(LuceneQuery query, JsonNode q }), }; field.Metadata.Add("Name", name); - typetype.AddField(field); + typeType.AddField(field); } else if (type == "integer") { @@ -134,7 +137,7 @@ private static FieldType BuildSchemaBasedFieldType(LuceneQuery query, JsonNode q }; field.Metadata.Add("Name", name); - typetype.AddField(field); + typeType.AddField(field); } } @@ -146,15 +149,17 @@ private static FieldType BuildSchemaBasedFieldType(LuceneQuery query, JsonNode q Name = fieldTypeName, Description = "Represents the " + query.Source + " Query : " + query.Name, - ResolvedType = new ListGraphType(typetype), + ResolvedType = new ListGraphType(typeType), Resolver = new LockedAsyncFieldResolver(ResolveAsync), Type = typeof(ListGraphType>) }; async ValueTask ResolveAsync(IResolveFieldContext context) { - var queryManager = context.RequestServices.GetService(); - var iquery = await queryManager.GetQueryAsync(query.Name); + var session = context.RequestServices.GetService(); + var querySources = context.RequestServices.GetServices(); + + var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); var parameters = context.GetArgument("parameters"); @@ -162,7 +167,10 @@ async ValueTask ResolveAsync(IResolveFieldContext context) JConvert.DeserializeObject>(parameters) : []; - var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters); + var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) + ?? throw new ArgumentException("Query source not found: " + query.Source); + + var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; } @@ -172,9 +180,9 @@ async ValueTask ResolveAsync(IResolveFieldContext context) private static FieldType BuildContentTypeFieldType(ISchema schema, string contentType, LuceneQuery query, string fieldTypeName) { - var typetype = schema.Query.Fields.OfType().FirstOrDefault(x => x.Name == contentType); + var typeType = schema.Query.Fields.OfType().FirstOrDefault(x => x.Name == contentType); - if (typetype == null) + if (typeType == null) { return null; } @@ -187,15 +195,17 @@ private static FieldType BuildContentTypeFieldType(ISchema schema, string conten Name = fieldTypeName, Description = "Represents the " + query.Source + " Query : " + query.Name, - ResolvedType = typetype.ResolvedType, + ResolvedType = typeType.ResolvedType, Resolver = new LockedAsyncFieldResolver(ResolveAsync), - Type = typetype.Type + Type = typeType.Type }; async ValueTask ResolveAsync(IResolveFieldContext context) { - var queryManager = context.RequestServices.GetService(); - var iquery = await queryManager.GetQueryAsync(query.Name); + var session = context.RequestServices.GetService(); + var querySources = context.RequestServices.GetServices(); + + var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); var parameters = context.GetArgument("parameters"); @@ -203,7 +213,10 @@ async ValueTask ResolveAsync(IResolveFieldContext context) JConvert.DeserializeObject>(parameters) : []; - var result = await queryManager.ExecuteQueryAsync(iquery, queryParameters); + var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) + ?? throw new ArgumentException("Query source not found: " + query.Source); + + var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; } diff --git a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/OrchardCore.Search.Lucene.csproj b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/OrchardCore.Search.Lucene.csproj index 85d650345f4..08165054031 100644 --- a/src/OrchardCore.Modules/OrchardCore.Search.Lucene/OrchardCore.Search.Lucene.csproj +++ b/src/OrchardCore.Modules/OrchardCore.Search.Lucene/OrchardCore.Search.Lucene.csproj @@ -31,6 +31,7 @@ + diff --git a/src/OrchardCore/OrchardCore.Queries.Abstractions/IQueryManager.cs b/src/OrchardCore/OrchardCore.Queries.Abstractions/IQueryManager.cs deleted file mode 100644 index 81d6da64282..00000000000 --- a/src/OrchardCore/OrchardCore.Queries.Abstractions/IQueryManager.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace OrchardCore.Queries -{ - /// - /// Contract for managing the query. - /// - public interface IQueryManager - { - /// - /// Returns a list of all store . - /// - Task> ListQueriesAsync(); - - /// - /// Saves the specific . - /// - /// The name of the query to update. - /// The instance to save. - Task SaveQueryAsync(string name, Query query); - - /// - /// Deletes the specified . - /// - /// The name of the query to delete. - Task DeleteQueryAsync(string name); - - /// - /// Returns the instance with the specified name for update. - /// - Task LoadQueryAsync(string name); - - /// - /// Gets the instance with the specified name in read-only. - /// - /// - /// - Task GetQueryAsync(string name); - - /// - /// Executes a query. - /// - /// The query to execute. - /// The parameters for the query. - /// The result of the query. - Task ExecuteQueryAsync(Query query, IDictionary parameters); - - /// - /// Returns an unique identifier that is updated when queries have changed. - /// - Task GetIdentifierAsync(); - } -} diff --git a/src/OrchardCore/OrchardCore.Queries.Abstractions/OrchardCore.Queries.Abstractions.csproj b/src/OrchardCore/OrchardCore.Queries.Abstractions/OrchardCore.Queries.Abstractions.csproj index 44a971cdf88..637f2d73920 100644 --- a/src/OrchardCore/OrchardCore.Queries.Abstractions/OrchardCore.Queries.Abstractions.csproj +++ b/src/OrchardCore/OrchardCore.Queries.Abstractions/OrchardCore.Queries.Abstractions.csproj @@ -1,12 +1,14 @@ - + OrchardCore.Queries OrchardCore Queries Abstractions - $(OCCMSDescription) + + $(OCCMSDescription) - Abstractions for Queries. + Abstractions for Queries. + $(PackageTags) OrchardCoreCMS ContentManagement diff --git a/src/OrchardCore/OrchardCore.Queries.Abstractions/IQuerySource.cs b/src/OrchardCore/OrchardCore.Queries.Core/IQuerySource.cs similarity index 100% rename from src/OrchardCore/OrchardCore.Queries.Abstractions/IQuerySource.cs rename to src/OrchardCore/OrchardCore.Queries.Core/IQuerySource.cs diff --git a/src/OrchardCore/OrchardCore.Queries.Core/Indexes/QueryIndex.cs b/src/OrchardCore/OrchardCore.Queries.Core/Indexes/QueryIndex.cs new file mode 100644 index 00000000000..a126f8bb432 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Queries.Core/Indexes/QueryIndex.cs @@ -0,0 +1,24 @@ +using YesSql.Indexes; + +namespace OrchardCore.Queries.Indexes; + +public class QueryIndex : MapIndex +{ + public string Name { get; set; } + + public string Source { get; set; } +} + +public class QueryIndexProvider : IndexProvider +{ + public override void Describe(DescribeContext context) + { + context + .For() + .Map(x => new QueryIndex + { + Name = x.Name, + Source = x.Source, + }); + } +} diff --git a/src/OrchardCore/OrchardCore.Queries.Core/OrchardCore.Queries.Core.csproj b/src/OrchardCore/OrchardCore.Queries.Core/OrchardCore.Queries.Core.csproj new file mode 100644 index 00000000000..7d4f1f1c894 --- /dev/null +++ b/src/OrchardCore/OrchardCore.Queries.Core/OrchardCore.Queries.Core.csproj @@ -0,0 +1,27 @@ + + + + + OrchardCore Queries Core + + $(OCCMSDescription) + + Core implementation for Queries. + + $(PackageTags) OrchardCoreCMS Queries + + + + + + + + + + + + + + + + diff --git a/src/OrchardCore/OrchardCore.Queries.Abstractions/Query.cs b/src/OrchardCore/OrchardCore.Queries.Core/Query.cs similarity index 74% rename from src/OrchardCore/OrchardCore.Queries.Abstractions/Query.cs rename to src/OrchardCore/OrchardCore.Queries.Core/Query.cs index 0d3e59062be..973cfbcbe1b 100644 --- a/src/OrchardCore/OrchardCore.Queries.Abstractions/Query.cs +++ b/src/OrchardCore/OrchardCore.Queries.Core/Query.cs @@ -1,9 +1,12 @@ +using System.Collections.Generic; +using OrchardCore.Entities; + namespace OrchardCore.Queries { /// /// Represents a query. /// - public class Query + public class Query : Entity { /// /// Initializes a new instance of a . @@ -14,6 +17,10 @@ protected Query(string source) Source = source; } + public Query() + { + } + /// /// Gets or sets the technical name of the query. /// @@ -22,7 +29,7 @@ protected Query(string source) /// /// Gets the name of the source for this query. /// - public string Source { get; } + public string Source { get; set; } /// /// Gets or sets the return schema of the query. @@ -32,4 +39,11 @@ protected Query(string source) public virtual bool ResultsOfType() => typeof(T) == typeof(object); } + + public class QueryPageResult + { + public int? Count { get; set; } + + public IEnumerable Queries { get; set; } + } } diff --git a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs index 6b6a8b7e8fb..4e4907daeb8 100644 --- a/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs +++ b/src/OrchardCore/OrchardCore.Search.Elasticsearch.Abstractions/IElasticQueryService.cs @@ -1,13 +1,12 @@ using System.Threading.Tasks; -using OrchardCore.Queries; namespace OrchardCore.Search.Elasticsearch { public interface IElasticQueryService { /// - /// Provides a way to execute an OC in Elasticsearch. - /// OC implementation deserializes the JSON string to an Elasticsearch SearchRequest. + /// Provides a way to execute an OC query in Elasticsearch. + /// OC implementation deserializes the query JSON string to an Elasticsearch SearchRequest. /// Also provides a way to return only specific fields: /// The fields option. /// From 877da6a653c519259cc2ca05c723317453956a33 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Wed, 3 Jul 2024 15:36:45 -0700 Subject: [PATCH 02/45] Convert everything --- .../Controllers/AdminController.cs | 13 +- .../Controllers/QueryApiController.cs | 20 +++- .../Deployment/AllQueriesDeploymentSource.cs | 10 +- .../QueryBasedContentDeploymentSource.cs | 20 +++- .../QueryBasedContentDeploymentStepDriver.cs | 14 ++- .../Drivers/QueryDisplayDriver.cs | 28 +++-- .../OrchardCore.Queries/Liquid/QueryFilter.cs | 12 +- .../Migrations/QueryMigrations.cs | 37 ++++++ .../OrchardCore.Queries/Permissions.cs | 17 +-- .../QueryGlobalMethodProvider.cs | 14 ++- .../QueryOrchardRazorHelperExtensions.cs | 20 +++- .../OrchardCore.Queries/Recipes/QueryStep.cs | 21 ++-- .../Services/QueryManager.cs | 111 ------------------ .../Sql/Drivers/SqlQueryDisplayDriver.cs | 8 +- .../Sql/GraphQL/SqlQueryFieldTypeProvider.cs | 14 +-- .../Sql/Migrations/SqlQueryMigrations.cs | 81 +++++++++++++ .../Sql/Models/SqlQueryMetadata.cs | 2 - .../OrchardCore.Queries/Sql/SqlQuery.cs | 5 +- .../OrchardCore.Queries/Sql/SqlQuerySource.cs | 5 +- .../OrchardCore.Queries/Sql/Startup.cs | 8 +- .../OrchardCore.Queries/Startup.cs | 16 ++- ...ueryBasedContentDeploymentStepViewModel.cs | 8 ++ ...edContentDeploymentStep.Fields.Edit.cshtml | 3 +- .../SqlQuery.Buttons.SummaryAdmin.cshtml | 7 +- .../Controllers/ElasticsearchApiController.cs | 19 ++- .../Drivers/ElasticQueryDisplayDriver.cs | 58 ++++++--- .../GraphQL/ElasticQueryFieldTypeProvider.cs | 24 ++-- .../ElasticsearchQueryMigrations.cs | 83 +++++++++++++ .../Startup.cs | 3 + .../ElasticQuery.Buttons.SummaryAdmin.cshtml | 10 +- .../Controllers/LuceneApiController.cs | 16 ++- .../Drivers/LuceneQueryDisplayDriver.cs | 57 ++++++--- .../GraphQL/LuceneQueryFieldTypeProvider.cs | 21 ++-- .../Migrations/LuceneQueryMigrations.cs | 83 +++++++++++++ .../Model/LuceneQueryMetadata.cs | 8 ++ .../OrchardCore.Search.Lucene.csproj | 6 +- .../Services/LuceneQuery.cs | 9 +- .../Services/LuceneQuerySource.cs | 25 ++-- .../OrchardCore.Search.Lucene/Startup.cs | 7 +- .../LuceneQuery.Buttons.SummaryAdmin.cshtml | 8 +- .../{QueryIndex.cs => QueryIndexProvider.cs} | 4 +- .../OrchardCore.Queries.Core.csproj | 1 + .../OrchardCore.Queries.Core/Query.cs | 13 +- .../Models/ElasticQuery.cs | 9 +- .../Models/ElasticsearchQueryMetadata.cs | 8 ++ ...chardCore.Search.Elasticsearch.Core.csproj | 2 +- .../ServiceCollectionExtensions.cs | 7 +- .../Services/ElasticQuerySource.cs | 23 ++-- 48 files changed, 681 insertions(+), 317 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.Queries/Migrations/QueryMigrations.cs delete mode 100644 src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Queries/Sql/Migrations/SqlQueryMigrations.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Search.Elasticsearch/Migrations/ElasticsearchQueryMigrations.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Search.Lucene/Migrations/LuceneQueryMigrations.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Search.Lucene/Model/LuceneQueryMetadata.cs rename src/OrchardCore/OrchardCore.Queries.Core/Indexes/{QueryIndex.cs => QueryIndexProvider.cs} (74%) create mode 100644 src/OrchardCore/OrchardCore.Search.Elasticsearch.Core/Models/ElasticsearchQueryMetadata.cs diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs index aa92c7763af..4bfb1c48e67 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/AdminController.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using OrchardCore.Admin; @@ -30,7 +31,7 @@ public class AdminController : Controller private readonly IAuthorizationService _authorizationService; private readonly PagerOptions _pagerOptions; private readonly INotifier _notifier; - private readonly IEnumerable _querySources; + private readonly IServiceProvider _serviceProvider; private readonly ISession _session; private readonly IDisplayManager _displayManager; private readonly IUpdateModelAccessor _updateModelAccessor; @@ -48,14 +49,14 @@ public AdminController( IStringLocalizer stringLocalizer, IHtmlLocalizer htmlLocalizer, INotifier notifier, - IEnumerable querySources, + IServiceProvider serviceProvider, IUpdateModelAccessor updateModelAccessor) { _session = session; _displayManager = displayManager; _authorizationService = authorizationService; _pagerOptions = pagerOptions.Value; - _querySources = querySources; + _serviceProvider = serviceProvider; _updateModelAccessor = updateModelAccessor; _shapeFactory = shapeFactory; _notifier = notifier; @@ -94,7 +95,7 @@ public async Task Index(ContentOptions options, PagerParameters p Queries = [], Options = options, Pager = await _shapeFactory.PagerAsync(pager, count, routeData), - QuerySourceNames = _querySources.Select(x => x.Name).ToList() + QuerySourceNames = _serviceProvider.GetServices().Select(x => x.Name).ToList() }; foreach (var qry in queries) @@ -130,7 +131,7 @@ public async Task Create(string id) return Forbid(); } - var query = _querySources.FirstOrDefault(x => x.Name == id)?.Create(); + var query = _serviceProvider.GetKeyedService(id)?.Create(); if (query == null) { @@ -154,7 +155,7 @@ public async Task CreatePost(QueriesCreateViewModel model) return Forbid(); } - var query = _querySources.FirstOrDefault(x => x.Name == model.SourceName)?.Create(); + var query = _serviceProvider.GetKeyedService(model.SourceName)?.Create(); if (query == null) { diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/QueryApiController.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/QueryApiController.cs index 14b8b90aed7..6fc47f5eb0e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/QueryApiController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Controllers/QueryApiController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -6,6 +7,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Queries.Indexes; +using YesSql; namespace OrchardCore.Queries.Controllers { @@ -14,16 +18,19 @@ namespace OrchardCore.Queries.Controllers [Authorize(AuthenticationSchemes = "Api"), IgnoreAntiforgeryToken, AllowAnonymous] public class QueryApiController : ControllerBase { + private readonly YesSql.ISession _session; + private readonly IServiceProvider _serviceProvider; private readonly IAuthorizationService _authorizationService; - private readonly IQueryManager _queryManager; public QueryApiController( IAuthorizationService authorizationService, - IQueryManager queryManager + YesSql.ISession session, + IServiceProvider serviceProvider ) { _authorizationService = authorizationService; - _queryManager = queryManager; + _session = session; + _serviceProvider = serviceProvider; } [HttpPost, HttpGet] @@ -32,7 +39,7 @@ public async Task Query( string name, string parameters) { - var query = await _queryManager.GetQueryAsync(name); + var query = await _session.Query(q => q.Name == name).FirstOrDefaultAsync(); if (query == null) { @@ -56,7 +63,10 @@ public async Task Query( JConvert.DeserializeObject>(parameters) : []; - var result = await _queryManager.ExecuteQueryAsync(query, queryParameters); + var querySource = _serviceProvider.GetRequiredKeyedService(query.Source); + + var result = await querySource.ExecuteQueryAsync(query, queryParameters); + return new ObjectResult(result); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/AllQueriesDeploymentSource.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/AllQueriesDeploymentSource.cs index ebcb38dcb46..107cd0967ee 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/AllQueriesDeploymentSource.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/AllQueriesDeploymentSource.cs @@ -4,19 +4,21 @@ using Microsoft.Extensions.Options; using OrchardCore.Deployment; using OrchardCore.Json; +using OrchardCore.Queries.Indexes; +using YesSql; namespace OrchardCore.Queries.Deployment { public class AllQueriesDeploymentSource : IDeploymentSource { - private readonly IQueryManager _queryManager; + private readonly ISession _session; private readonly JsonSerializerOptions _jsonSerializerOptions; public AllQueriesDeploymentSource( - IQueryManager queryManager, + ISession session, IOptions jsonSerializerOptions) { - _queryManager = queryManager; + _session = session; _jsonSerializerOptions = jsonSerializerOptions.Value.SerializerOptions; } @@ -29,7 +31,7 @@ public async Task ProcessDeploymentStepAsync(DeploymentStep step, DeploymentPlan return; } - var queries = await _queryManager.ListQueriesAsync(); + var queries = await _session.Query().ListAsync(); result.Steps.Add(new JsonObject { diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentSource.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentSource.cs index 7add2e2caaa..51335b7292b 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentSource.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentSource.cs @@ -1,19 +1,27 @@ +using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using OrchardCore.ContentManagement; using OrchardCore.Deployment; +using OrchardCore.Queries.Indexes; +using YesSql; namespace OrchardCore.Queries.Deployment { public class QueryBasedContentDeploymentSource : IDeploymentSource { - private readonly IQueryManager _queryManager; + private readonly ISession _session; + private readonly IServiceProvider _serviceProvider; - public QueryBasedContentDeploymentSource(IQueryManager queryManager) + public QueryBasedContentDeploymentSource( + ISession session, + IServiceProvider serviceProvider) { - _queryManager = queryManager; + _session = session; + _serviceProvider = serviceProvider; } public async Task ProcessDeploymentStepAsync(DeploymentStep step, DeploymentPlanResult result) @@ -27,7 +35,7 @@ public async Task ProcessDeploymentStepAsync(DeploymentStep step, DeploymentPlan var data = new JsonArray(); - var query = await _queryManager.GetQueryAsync(queryDeploymentStep.QueryName); + var query = await _session.Query(q => q.Name == queryDeploymentStep.QueryName).FirstOrDefaultAsync(); if (query == null) { @@ -44,7 +52,9 @@ public async Task ProcessDeploymentStepAsync(DeploymentStep step, DeploymentPlan return; } - var results = await _queryManager.ExecuteQueryAsync(query, parameters); + var querySource = _serviceProvider.GetRequiredKeyedService(query.Source); + + var results = await querySource.ExecuteQueryAsync(query, parameters); foreach (var contentItem in results.Items) { diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentStepDriver.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentStepDriver.cs index 43b71b41959..80866fe2fba 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentStepDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Deployment/QueryBasedContentDeploymentStepDriver.cs @@ -8,20 +8,23 @@ using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Views; using OrchardCore.Mvc.ModelBinding; +using OrchardCore.Queries.Indexes; using OrchardCore.Queries.ViewModels; +using YesSql; namespace OrchardCore.Queries.Deployment { public class QueryBasedContentDeploymentStepDriver : DisplayDriver { - private readonly IQueryManager _queryManager; + private readonly ISession _session; + protected readonly IStringLocalizer S; public QueryBasedContentDeploymentStepDriver( - IQueryManager queryManager, + ISession session, IStringLocalizer stringLocalizer) { - _queryManager = queryManager; + _session = session; S = stringLocalizer; } @@ -36,11 +39,12 @@ public override IDisplayResult Display(QueryBasedContentDeploymentStep step) public override IDisplayResult Edit(QueryBasedContentDeploymentStep step) { - return Initialize("QueryBasedContentDeploymentStep_Fields_Edit", model => + return Initialize("QueryBasedContentDeploymentStep_Fields_Edit", async model => { model.QueryName = step.QueryName; model.QueryParameters = step.QueryParameters; model.ExportAsSetupRecipe = step.ExportAsSetupRecipe; + model.Queries = await _session.Query().OrderBy(q => q.Name).ListAsync(); }).Location("Content"); } @@ -48,7 +52,7 @@ public override async Task UpdateAsync(QueryBasedContentDeployme { var queryBasedContentViewModel = new QueryBasedContentDeploymentStepViewModel(); await updater.TryUpdateModelAsync(queryBasedContentViewModel, Prefix, viewModel => viewModel.QueryName, viewModel => viewModel.QueryParameters, viewModel => viewModel.ExportAsSetupRecipe); - var query = await _queryManager.LoadQueryAsync(queryBasedContentViewModel.QueryName); + var query = await _session.Query(q => q.Name == queryBasedContentViewModel.QueryName).FirstOrDefaultAsync(); if (!query.ResultsOfType()) { diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Drivers/QueryDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Drivers/QueryDisplayDriver.cs index 1253b8f681c..302af244892 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Drivers/QueryDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Drivers/QueryDisplayDriver.cs @@ -3,19 +3,26 @@ using OrchardCore.DisplayManagement.Handlers; using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Views; +using OrchardCore.Mvc.ModelBinding; using OrchardCore.Mvc.Utilities; +using OrchardCore.Queries.Indexes; +using OrchardCore.Queries.Migrations; using OrchardCore.Queries.ViewModels; +using YesSql; namespace OrchardCore.Queries.Drivers { public class QueryDisplayDriver : DisplayDriver { - private readonly IQueryManager _queryManager; + private readonly ISession _session; + protected readonly IStringLocalizer S; - public QueryDisplayDriver(IQueryManager queryManager, IStringLocalizer stringLocalizer) + public QueryDisplayDriver( + ISession session, + IStringLocalizer stringLocalizer) { - _queryManager = queryManager; + _session = session; S = stringLocalizer; } @@ -65,24 +72,29 @@ public override async Task UpdateAsync(Query model, IUpdateModel if (string.IsNullOrEmpty(model.Name)) { - updater.ModelState.AddModelError(nameof(model.Name), S["Name is required"]); + updater.ModelState.AddModelError(Prefix, nameof(model.Name), S["Name is required"]); + } + else if (model.Name.Length > QueryMigrations.MaxQueryNameLength) + { + updater.ModelState.AddModelError(Prefix, nameof(model.Name), S["Name must be less than or equals {0} characters in length.", QueryMigrations.MaxQueryNameLength]); } + if (!string.IsNullOrEmpty(model.Schema) && !model.Schema.IsJson()) { - updater.ModelState.AddModelError(nameof(model.Schema), S["Invalid schema JSON supplied."]); + updater.ModelState.AddModelError(Prefix, nameof(model.Schema), S["Invalid schema JSON supplied."]); } var safeName = model.Name.ToSafeName(); if (string.IsNullOrEmpty(safeName) || model.Name != safeName) { - updater.ModelState.AddModelError(nameof(model.Name), S["Name contains illegal characters"]); + updater.ModelState.AddModelError(Prefix, nameof(model.Name), S["Name contains illegal characters"]); } else { - var existing = await _queryManager.LoadQueryAsync(safeName); + var existing = await _session.Query(q => q.Name == safeName).FirstOrDefaultAsync(); if (existing != null && existing != model) { - updater.ModelState.AddModelError(nameof(model.Name), S["A query with the same name already exists"]); + updater.ModelState.AddModelError(Prefix, nameof(model.Name), S["A query with the same name already exists"]); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Liquid/QueryFilter.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Liquid/QueryFilter.cs index 7d3f586abe6..378956b496c 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Liquid/QueryFilter.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Liquid/QueryFilter.cs @@ -1,18 +1,20 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using Fluid; using Fluid.Values; +using Microsoft.Extensions.DependencyInjection; using OrchardCore.Liquid; namespace OrchardCore.Queries.Liquid { public class QueryFilter : ILiquidFilter { - private readonly IQueryManager _queryManager; + private readonly IServiceProvider _serviceProvider; - public QueryFilter(IQueryManager queryManager) + public QueryFilter(IServiceProvider serviceProvider) { - _queryManager = queryManager; + _serviceProvider = serviceProvider; } public async ValueTask ProcessAsync(FluidValue input, FilterArguments arguments, LiquidTemplateContext ctx) @@ -31,7 +33,9 @@ public async ValueTask ProcessAsync(FluidValue input, FilterArgument parameters.Add(name, arguments[name].ToObjectValue()); } - var result = await _queryManager.ExecuteQueryAsync(query, parameters); + var querySource = _serviceProvider.GetRequiredKeyedService(query.Source); + + var result = await querySource.ExecuteQueryAsync(query, parameters); return FluidValue.Create(result.Items, ctx.Options); } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Migrations/QueryMigrations.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Migrations/QueryMigrations.cs new file mode 100644 index 00000000000..11b13f2f59e --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Migrations/QueryMigrations.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using OrchardCore.Data.Migration; +using OrchardCore.Queries.Indexes; +using YesSql.Sql; + +namespace OrchardCore.Queries.Migrations; + +public class QueryMigrations : DataMigration +{ + public const int MaxQuerySourceLength = 100; + public const int MaxQueryNameLength = 200; + + public async Task CreateAsync() + { + await SchemaBuilder.CreateMapIndexTableAsync(table => table + .Column("Source", column => column.WithLength(MaxQuerySourceLength)) + .Column("Name", column => column.WithLength(MaxQueryNameLength)) + ); + + await SchemaBuilder.AlterIndexTableAsync(table => table + .CreateIndex("IDX_QueryIndex_DocumentId_Source", + "DocumentId", + "Source", + "Name" + ) + ); + + await SchemaBuilder.AlterIndexTableAsync(table => table + .CreateIndex("IDX_QueryIndex_DocumentId_Name", + "DocumentId", + "Name" + ) + ); + + return 1; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Permissions.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Permissions.cs index 2b197ba176d..24c6446ce83 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Permissions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Permissions.cs @@ -1,26 +1,27 @@ using System.Collections.Generic; using System.Threading.Tasks; +using OrchardCore.Queries.Indexes; using OrchardCore.Security.Permissions; +using YesSql; namespace OrchardCore.Queries; public sealed class Permissions : IPermissionProvider { public static readonly Permission ManageQueries = new("ManageQueries", "Manage queries"); - public static readonly Permission ExecuteApiAll = new("ExecuteApiAll", "Execute Api - All queries", new[] { ManageQueries }); + public static readonly Permission ExecuteApiAll = new("ExecuteApiAll", "Execute Api - All queries", [ManageQueries]); - private static readonly Permission _executeApi = new("ExecuteApi_{0}", "Execute Api - {0}", new[] { ManageQueries, ExecuteApiAll }); + private static readonly Permission _executeApi = new("ExecuteApi_{0}", "Execute Api - {0}", [ManageQueries, ExecuteApiAll]); + private readonly ISession _session; private readonly IEnumerable _generalPermissions = [ ManageQueries, ]; - private readonly IQueryManager _queryManager; - - public Permissions(IQueryManager queryManager) + public Permissions(ISession session) { - _queryManager = queryManager; + _session = session; } public async Task> GetPermissionsAsync() @@ -31,7 +32,9 @@ public async Task> GetPermissionsAsync() ExecuteApiAll, }; - foreach (var query in await _queryManager.ListQueriesAsync()) + var queries = await _session.Query().ListAsync(); + + foreach (var query in queries) { list.Add(CreatePermissionForQuery(query.Name)); } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/QueryGlobalMethodProvider.cs b/src/OrchardCore.Modules/OrchardCore.Queries/QueryGlobalMethodProvider.cs index b9fdd9c044a..7acc2f89e48 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/QueryGlobalMethodProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/QueryGlobalMethodProvider.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Queries.Indexes; using OrchardCore.Scripting; +using YesSql; namespace OrchardCore.Queries { @@ -20,14 +22,18 @@ public QueryGlobalMethodProvider() Name = "executeQuery", Method = serviceProvider => (Func)((name, parameters) => { - var queryManager = serviceProvider.GetRequiredService(); - var query = queryManager.GetQueryAsync(name).GetAwaiter().GetResult(); + var session = serviceProvider.GetRequiredService(); + var query = session.Query(q => q.Name == name).FirstOrDefaultAsync().GetAwaiter().GetResult(); + if (query == null) { return null; } - var result = queryManager.ExecuteQueryAsync(query, (IDictionary)parameters).GetAwaiter().GetResult(); + var querySource = serviceProvider.GetRequiredKeyedService(query.Source); + + var result = querySource.ExecuteQueryAsync(query, (IDictionary)parameters).GetAwaiter().GetResult(); + return result.Items; }), }; @@ -35,7 +41,7 @@ public QueryGlobalMethodProvider() public IEnumerable GetMethods() { - return new[] { _executeQuery }; + return [_executeQuery]; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Razor/QueryOrchardRazorHelperExtensions.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Razor/QueryOrchardRazorHelperExtensions.cs index 375e90c5f34..5a3b38700db 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Razor/QueryOrchardRazorHelperExtensions.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Razor/QueryOrchardRazorHelperExtensions.cs @@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection; using OrchardCore; using OrchardCore.Queries; +using OrchardCore.Queries.Indexes; +using YesSql; #pragma warning disable CA1050 // Declare types in namespaces public static class QueryOrchardRazorHelperExtensions @@ -16,31 +18,37 @@ public static Task QueryAsync(this IOrchardHelper orchardHelper, st public static async Task QueryAsync(this IOrchardHelper orchardHelper, string queryName, IDictionary parameters) { - var queryManager = orchardHelper.HttpContext.RequestServices.GetService(); + var session = orchardHelper.HttpContext.RequestServices.GetService(); - var query = await queryManager.GetQueryAsync(queryName); + var query = await session.Query(q => q.Name == queryName).FirstOrDefaultAsync(); if (query == null) { return null; } - var result = await queryManager.ExecuteQueryAsync(query, parameters); + var querySource = orchardHelper.HttpContext.RequestServices.GetRequiredKeyedService(query.Source); + + var result = await querySource.ExecuteQueryAsync(query, parameters); + return result.Items; } public static async Task QueryResultsAsync(this IOrchardHelper orchardHelper, string queryName, IDictionary parameters) { - var queryManager = orchardHelper.HttpContext.RequestServices.GetService(); + var session = orchardHelper.HttpContext.RequestServices.GetService(); - var query = await queryManager.GetQueryAsync(queryName); + var query = await session.Query(q => q.Name == queryName).FirstOrDefaultAsync(); if (query == null) { return null; } - var result = await queryManager.ExecuteQueryAsync(query, parameters); + var querySource = orchardHelper.HttpContext.RequestServices.GetRequiredKeyedService(query.Source); + + var result = await querySource.ExecuteQueryAsync(query, parameters); + return result; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Recipes/QueryStep.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Recipes/QueryStep.cs index 116f5636975..bc2969c31fb 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Recipes/QueryStep.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Recipes/QueryStep.cs @@ -1,14 +1,15 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrchardCore.Json; using OrchardCore.Recipes.Models; using OrchardCore.Recipes.Services; +using YesSql; namespace OrchardCore.Queries.Recipes { @@ -17,19 +18,19 @@ namespace OrchardCore.Queries.Recipes /// public class QueryStep : IRecipeStepHandler { - private readonly IQueryManager _queryManager; - private readonly IEnumerable _querySources; + private readonly ISession _session; + private readonly IServiceProvider _serviceProvider; private readonly JsonSerializerOptions _jsonSerializerOptions; private readonly ILogger _logger; public QueryStep( - IQueryManager queryManager, - IEnumerable querySources, + ISession session, + IServiceProvider serviceProvider, IOptions jsonSerializerOptions, ILogger logger) { - _queryManager = queryManager; - _querySources = querySources; + _session = session; + _serviceProvider = serviceProvider; _jsonSerializerOptions = jsonSerializerOptions.Value.SerializerOptions; _logger = logger; } @@ -46,7 +47,7 @@ public async Task ExecuteAsync(RecipeExecutionContext context) foreach (var token in model.Queries.Cast()) { var sourceName = token[nameof(Query.Source)].ToString(); - var sample = _querySources.FirstOrDefault(x => x.Name == sourceName)?.Create(); + var sample = _serviceProvider.GetKeyedService(sourceName)?.Create(); if (sample == null) { @@ -56,8 +57,10 @@ public async Task ExecuteAsync(RecipeExecutionContext context) } var query = token.ToObject(sample.GetType(), _jsonSerializerOptions) as Query; - await _queryManager.SaveQueryAsync(query.Name, query); + await _session.SaveAsync(query); } + + await _session.SaveChangesAsync(); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs deleted file mode 100644 index 6c9678a22b4..00000000000 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Services/QueryManager.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using OrchardCore.Queries.Indexes; -using YesSql; - -namespace OrchardCore.Queries.Services -{ - public class QueryManager : IQueryManager - { - private readonly IEnumerable _querySources; - private readonly ISession _session; - - public QueryManager( - IEnumerable querySources, - ISession session) - { - _querySources = querySources; - _session = session; - } - - public async Task DeleteQueryAsync(string name) - { - ArgumentException.ThrowIfNullOrEmpty(name); - - await DeleteQueryInternalAsync(name, true); - } - - public async Task GetQueryAsync(string name) - { - ArgumentException.ThrowIfNullOrEmpty(name); - - var query = await _session.Query(x => x.Name == name).FirstOrDefaultAsync(); - - return query; - } - - public async Task ListQueriesAsync(Func predicate = null, int? page = null, int? pageSize = null) - { - var query = _session.Query(); - - if (predicate != null) - { - query = query.Where(q => predicate(q)); - } - - query = query - .OrderBy(x => x.Name) - .ThenBy(x => x.Id); - - if (page == null || page < 0) - { - page = 1; - } - - var count = await query.CountAsync(); - - if (pageSize > 0) - { - return new QueryPageResult() - { - Count = count, - Queries = await query.Take(pageSize.Value) - .Skip((page.Value - 1) * pageSize.Value) - .ListAsync(), - }; - } - - return new QueryPageResult() - { - Count = count, - Queries = await query.ListAsync(), - }; - } - - public async Task SaveQueryAsync(string name, Query query) - { - ArgumentException.ThrowIfNullOrEmpty(name); - - ArgumentNullException.ThrowIfNull(query); - - await DeleteQueryInternalAsync(name, false); - - await _session.SaveAsync(query); - } - - public Task ExecuteQueryAsync(Query query, IDictionary parameters) - { - var querySource = _querySources.FirstOrDefault(q => q.Name == query.Source) - ?? throw new ArgumentException("Query source not found: " + query.Source); - - return querySource.ExecuteQueryAsync(query, parameters); - } - - private async Task DeleteQueryInternalAsync(string name, bool commit) - { - var queries = await _session.Query(x => x.Name == name).ListAsync(); - - foreach (var query in queries) - { - _session.Delete(query); - } - - if (commit && queries.Any()) - { - await _session.SaveChangesAsync(); - } - } - } -} diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs index 51ee5433304..e900347e2f4 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Drivers/SqlQueryDisplayDriver.cs @@ -4,6 +4,7 @@ using OrchardCore.DisplayManagement.ModelBinding; using OrchardCore.DisplayManagement.Views; using OrchardCore.Entities; +using OrchardCore.Mvc.ModelBinding; using OrchardCore.Queries.Sql.Models; using OrchardCore.Queries.Sql.ViewModels; @@ -46,9 +47,10 @@ public override IDisplayResult Edit(Query query, IUpdateModel updater) return Initialize("SqlQuery_Edit", model => { + model.ReturnDocuments = query.ReturnContentItems; + var metadata = query.As(); model.Query = metadata.Template; - model.ReturnDocuments = metadata.ReturnDocuments; // Extract query from the query string if we come from the main query editor. if (string.IsNullOrEmpty(metadata.Template)) @@ -70,13 +72,13 @@ public override async Task UpdateAsync(Query query, IUpdateModel if (string.IsNullOrWhiteSpace(viewModel.Query)) { - updater.ModelState.AddModelError(nameof(viewModel.Query), S["The query field is required"]); + updater.ModelState.AddModelError(Prefix, nameof(viewModel.Query), S["The query field is required"]); } + query.ReturnContentItems = viewModel.ReturnDocuments; query.Put(new SqlQueryMetadata() { Template = viewModel.Query, - ReturnDocuments = viewModel.ReturnDocuments, }); return Edit(query, updater); diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs index afb99cc75de..254c28fe96d 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/GraphQL/SqlQueryFieldTypeProvider.cs @@ -43,7 +43,7 @@ public async Task BuildAsync(ISchema schema) { var session = _httpContextAccessor.HttpContext.RequestServices.GetService(); - var queries = await session.Query().ListAsync(); + var queries = await session.Query(q => q.Source == SqlQuerySource.SourceName).ListAsync(); foreach (var query in queries) { @@ -69,7 +69,7 @@ public async Task BuildAsync(ISchema schema) var fieldTypeName = querySchema["fieldTypeName"]?.ToString() ?? query.Name; - if (sqlQueryMetadata.ReturnDocuments && type.StartsWith("ContentItem/", StringComparison.OrdinalIgnoreCase)) + if (query.ReturnContentItems && type.StartsWith("ContentItem/", StringComparison.OrdinalIgnoreCase)) { var contentType = type.Remove(0, 12); fieldType = BuildContentTypeFieldType(schema, contentType, query, fieldTypeName); @@ -162,7 +162,7 @@ private static FieldType BuildSchemaBasedFieldType(Query query, JsonNode querySc async ValueTask ResolveAsync(IResolveFieldContext context) { var session = context.RequestServices.GetService(); - var querySources = context.RequestServices.GetServices(); + var querySource = context.RequestServices.GetRequiredKeyedService(query.Source); var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); @@ -172,9 +172,6 @@ async ValueTask ResolveAsync(IResolveFieldContext context) JConvert.DeserializeObject>(parameters) : []; - var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) - ?? throw new ArgumentException("Query source not found: " + query.Source); - var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; @@ -207,7 +204,7 @@ private static FieldType BuildContentTypeFieldType(ISchema schema, string conten async ValueTask ResolveAsync(IResolveFieldContext context) { var session = context.RequestServices.GetService(); - var querySources = context.RequestServices.GetServices(); + var querySource = context.RequestServices.GetRequiredKeyedService(query.Source); var iQuery = await session.Query(q => q.Name == query.Name).FirstOrDefaultAsync(); @@ -217,9 +214,6 @@ async ValueTask ResolveAsync(IResolveFieldContext context) JConvert.DeserializeObject>(parameters) : []; - var querySource = querySources.FirstOrDefault(q => q.Name == query.Source) - ?? throw new ArgumentException("Query source not found: " + query.Source); - var result = await querySource.ExecuteQueryAsync(iQuery, queryParameters); return result.Items; diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Migrations/SqlQueryMigrations.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Migrations/SqlQueryMigrations.cs new file mode 100644 index 00000000000..715d10fb94d --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Migrations/SqlQueryMigrations.cs @@ -0,0 +1,81 @@ +using System.Text.Json.Nodes; +using Dapper; +using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Data; +using OrchardCore.Data.Migration; +using OrchardCore.Entities; +using OrchardCore.Environment.Shell.Scope; +using OrchardCore.Queries.Sql.Models; +using YesSql; +using YesSql.Sql; + +namespace OrchardCore.Queries.Sql.Migrations; + +public class SqlQueryMigrations : DataMigration +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")] + public int Create() + { + ShellScope.AddDeferredTask(async scope => + { + var session = scope.ServiceProvider.GetRequiredService(); + var dbConnectionAccessor = scope.ServiceProvider.GetService(); + + var documentTableName = session.Store.Configuration.TableNameConvention.GetDocumentTable(); + var table = $"{session.Store.Configuration.TablePrefix}{documentTableName}"; + var dialect = session.Store.Configuration.SqlDialect; + var quotedTableName = dialect.QuoteForTableName(table, session.Store.Configuration.Schema); + var quotedContentColumnName = dialect.QuoteForColumnName("Content"); + var quotedTypeColumnName = dialect.QuoteForColumnName("Type"); + + var sqlBuilder = new SqlBuilder(session.Store.Configuration.TablePrefix, session.Store.Configuration.SqlDialect); + sqlBuilder.AddSelector(quotedContentColumnName); + sqlBuilder.From(quotedTableName); + sqlBuilder.WhereAnd($" {quotedTypeColumnName} = 'OrchardCore.Queries.Services.QueriesDocument, OrchardCore.Queries' "); + sqlBuilder.Take("1"); + + await using var connection = dbConnectionAccessor.CreateConnection(); + await connection.OpenAsync(); + var jsonContent = await connection.QueryFirstOrDefaultAsync(sqlBuilder.ToSqlString()); + + if (string.IsNullOrEmpty(jsonContent)) + { + return; + } + + var jsonObject = JsonNode.Parse(jsonContent); + + if (jsonObject["Queries"] is not JsonObject queriesObject) + { + return; + } + + foreach (var queryObject in queriesObject) + { + if (queryObject.Value["Source"].GetValue() != SqlQuerySource.SourceName) + { + continue; + } + + var query = new Query + { + Source = SqlQuerySource.SourceName, + Name = queryObject.Key, + Schema = queryObject.Value["Schema"].GetValue(), + ReturnContentItems = queryObject.Value["ReturnDocuments"].GetValue() + }; + + query.Put(new SqlQueryMetadata + { + Template = queryObject.Value["Template"].GetValue(), + }); + + await session.SaveAsync(query); + } + + await session.SaveChangesAsync(); + }); + + return 1; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs index c9c8a87fef4..768b9b08353 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/Models/SqlQueryMetadata.cs @@ -3,6 +3,4 @@ namespace OrchardCore.Queries.Sql.Models; public class SqlQueryMetadata { public string Template { get; set; } - - public bool ReturnDocuments { get; set; } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs index 3569543b2c1..25d39419c52 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuery.cs @@ -13,9 +13,6 @@ public SqlQuery() : base(SqlQuerySource.SourceName) [Obsolete("Use .As() instead to get this property value.")] public string Template { get; set; } - [Obsolete("Use .As() instead to get this property value.")] - public bool ReturnDocuments { get; set; } - - public override bool ResultsOfType() => ReturnDocuments ? typeof(T) == typeof(ContentItem) : base.ResultsOfType(); + public override bool ResultsOfType() => ReturnContentItems ? typeof(T) == typeof(ContentItem) : base.ResultsOfType(); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs index 6d41100f954..e5f1fc4eb4a 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Sql/SqlQuerySource.cs @@ -46,9 +46,10 @@ public string Name => SourceName; public Query Create() - => new Query() + => new() { Source = SourceName, + CanReturnContentItems = true, }; public async Task ExecuteQueryAsync(Query query, IDictionary parameters) @@ -73,7 +74,7 @@ public async Task ExecuteQueryAsync(Query query, IDictionary(); services.AddScoped, SqlQueryDisplayDriver>(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(sp => sp.GetService()); + services.AddKeyedScoped(SqlQuerySource.SourceName, (sp, key) => sp.GetService()); + services.AddScoped(); // Allows to serialize 'SqlQuery' from its base type. #pragma warning disable CS0618 // Type or member is obsolete services.AddJsonDerivedTypeInfo(); #pragma warning restore CS0618 // Type or member is obsolete + services.AddDataMigration(); } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Queries/Startup.cs index 8b4b4f56893..893d0eaabcc 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Startup.cs @@ -1,6 +1,8 @@ using Fluid; using Fluid.Values; using Microsoft.Extensions.DependencyInjection; +using OrchardCore.Data; +using OrchardCore.Data.Migration; using OrchardCore.Deployment; using OrchardCore.DisplayManagement.Handlers; using OrchardCore.Liquid; @@ -8,12 +10,14 @@ using OrchardCore.Navigation; using OrchardCore.Queries.Deployment; using OrchardCore.Queries.Drivers; +using OrchardCore.Queries.Indexes; using OrchardCore.Queries.Liquid; +using OrchardCore.Queries.Migrations; using OrchardCore.Queries.Recipes; -using OrchardCore.Queries.Services; using OrchardCore.Recipes; using OrchardCore.Scripting; using OrchardCore.Security.Permissions; +using YesSql; namespace OrchardCore.Queries { @@ -25,7 +29,6 @@ public sealed class Startup : StartupBase public override void ConfigureServices(IServiceCollection services) { services.AddScoped(); - services.AddScoped(); services.AddScoped, QueryDisplayDriver>(); services.AddRecipeExecutionStep(); services.AddScoped(); @@ -38,12 +41,17 @@ public override void ConfigureServices(IServiceCollection services) o.MemberAccessStrategy.Register(async (obj, name, context) => { var liquidTemplateContext = (LiquidTemplateContext)context; - var queryManager = liquidTemplateContext.Services.GetRequiredService(); + var session = liquidTemplateContext.Services.GetRequiredService(); - return FluidValue.Create(await queryManager.GetQueryAsync(name), context.Options); + var query = await session.Query(q => q.Name == name).FirstOrDefaultAsync(); + + return FluidValue.Create(query, context.Options); }); }) .AddLiquidFilter("query"); + + services.AddDataMigration(); + services.AddIndexProvider(); } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/ViewModels/QueryBasedContentDeploymentStepViewModel.cs b/src/OrchardCore.Modules/OrchardCore.Queries/ViewModels/QueryBasedContentDeploymentStepViewModel.cs index b814cc97a65..e29bd2621b8 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/ViewModels/QueryBasedContentDeploymentStepViewModel.cs +++ b/src/OrchardCore.Modules/OrchardCore.Queries/ViewModels/QueryBasedContentDeploymentStepViewModel.cs @@ -1,9 +1,17 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; + namespace OrchardCore.Queries.ViewModels { public class QueryBasedContentDeploymentStepViewModel { public string QueryName { get; set; } + public string QueryParameters { get; set; } = "{}"; + public bool ExportAsSetupRecipe { get; set; } + + [BindNever] + public IEnumerable Queries { get; set; } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Queries/Views/Items/QueryBasedContentDeploymentStep.Fields.Edit.cshtml b/src/OrchardCore.Modules/OrchardCore.Queries/Views/Items/QueryBasedContentDeploymentStep.Fields.Edit.cshtml index 4c04a5feb20..4eb54317535 100644 --- a/src/OrchardCore.Modules/OrchardCore.Queries/Views/Items/QueryBasedContentDeploymentStep.Fields.Edit.cshtml +++ b/src/OrchardCore.Modules/OrchardCore.Queries/Views/Items/QueryBasedContentDeploymentStep.Fields.Edit.cshtml @@ -1,7 +1,6 @@ @using OrchardCore.Queries.ViewModels @model QueryBasedContentDeploymentStepViewModel @inject OrchardCore.ContentManagement.Metadata.IContentDefinitionManager ContentDefinitionManager -@inject OrchardCore.Queries.IQueryManager QueryManager @@ -23,7 +22,7 @@