From f56ce6716294d3ee1e892c90a939b267582c5f50 Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Sat, 27 Jul 2024 20:27:42 +0300 Subject: [PATCH 1/2] feat: Add ability to get and search items without using cache --- .../Extensions/CrudServiceExtensions.cs | 36 ++++++++++ .../Extensions/SearchServiceExtensions.cs | 70 +++++++++++++++++++ .../GenericCrud/INoCacheCrudService.cs | 22 ++++++ .../GenericCrud/INoCacheSearchService.cs | 24 +++++++ .../GenericCrud/CrudService.cs | 27 ++++--- .../GenericCrud/SearchService.cs | 26 ++++++- 6 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs create mode 100644 src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs diff --git a/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs b/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs index 10d2c95faa4..0f21a6cb491 100644 --- a/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs +++ b/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs @@ -45,4 +45,40 @@ public static Task> GetByIdsAsync(this ICrudService + /// Returns data from the database without using cache. + /// + public static Task> GetNoCacheAsync(this ICrudService crudService, IList ids, string responseGroup = null, bool clone = true) + where TModel : Entity + { + return crudService.AsNoCache().GetNoCacheAsync(ids, responseGroup, clone); + } + + /// + /// Returns data from the database without using cache. + /// + public static async Task GetNoCacheAsync(this ICrudService crudService, string id, string responseGroup = null, bool clone = true) + where TModel : Entity + { + if (id is null) + { + return null; + } + + var entities = await crudService.AsNoCache().GetNoCacheAsync([id], responseGroup, clone); + + return entities?.FirstOrDefault(); + } + + public static INoCacheCrudService AsNoCache(this ICrudService crudService) + where TModel : Entity + { + if (crudService is not INoCacheCrudService noCacheService) + { + throw new NotSupportedException("Underlying service does not support no cache search."); + } + + return noCacheService; + } } diff --git a/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs b/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs index c97347b6bec..eff680dcbb1 100644 --- a/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs +++ b/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs @@ -102,5 +102,75 @@ public static async IAsyncEnumerable SearchBatchesAsync + /// Returns data from the database without using cache. + /// + public static Task SearchNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) + where TCriteria : SearchCriteriaBase + where TResult : GenericSearchResult + where TModel : IEntity + { + return searchService.AsNoCache().SearchNoCacheAsync(searchCriteria, clone); + } + + /// + /// Returns data from the database without using cache. + /// + public static async Task> SearchAllNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) + where TCriteria : SearchCriteriaBase + where TResult : GenericSearchResult + where TModel : IEntity + { + var result = new List(); + + await foreach (var searchResult in searchService.SearchBatchesNoCacheAsync(searchCriteria, clone)) + { + result.AddRange(searchResult.Results); + } + + return result; + } + + public static async IAsyncEnumerable SearchBatchesNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) + where TCriteria : SearchCriteriaBase + where TResult : GenericSearchResult + where TModel : IEntity + { + int totalCount; + searchCriteria = searchCriteria.CloneTyped(); + + do + { + var searchResult = await searchService.AsNoCache().SearchNoCacheAsync(searchCriteria, clone); + + if (searchCriteria.Take == 0 || searchResult.Results?.Count > 0) + { + yield return searchResult; + } + + if (searchCriteria.Take == 0) + { + yield break; + } + + totalCount = searchResult.TotalCount; + searchCriteria.Skip += searchCriteria.Take; + } + while (searchCriteria.Skip < totalCount); + } + + public static INoCacheSearchService AsNoCache(this ISearchService searchService) + where TCriteria : SearchCriteriaBase + where TResult : GenericSearchResult + where TModel : IEntity + { + if (searchService is not INoCacheSearchService noCacheService) + { + throw new NotSupportedException("Underlying service does not support no cache search."); + } + + return noCacheService; + } } } diff --git a/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs b/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs new file mode 100644 index 00000000000..03e09dff311 --- /dev/null +++ b/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.Platform.Core.GenericCrud; + +/// +/// Generic interface to use with CRUD services without using cache. +/// +/// +public interface INoCacheCrudService + where T : Entity +{ + /// + /// Returns a list of model instances for specified IDs without using cache. + /// + /// + /// + /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. + /// + Task> GetNoCacheAsync(IList ids, string responseGroup = null, bool clone = true); +} diff --git a/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs b/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs new file mode 100644 index 00000000000..f2f53693e51 --- /dev/null +++ b/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; +using VirtoCommerce.Platform.Core.Common; + +namespace VirtoCommerce.Platform.Core.GenericCrud; + +/// +/// Generic interface to use with search services without using cache +/// +/// +/// +/// +public interface INoCacheSearchService + where TCriteria : SearchCriteriaBase + where TResult : GenericSearchResult + where TModel : IEntity +{ + /// + /// Returns model instances that meet specified criteria without using cache. + /// + /// + /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. + /// + Task SearchNoCacheAsync(TCriteria criteria, bool clone = true); +} diff --git a/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs b/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs index 0e890f765f2..0a56384686d 100644 --- a/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs +++ b/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs @@ -22,7 +22,7 @@ namespace VirtoCommerce.Platform.Data.GenericCrud /// The type of data access layer entity (EF) /// The type of *change event /// The type of *changed event - public abstract class CrudService : ICrudService + public abstract class CrudService : ICrudService, INoCacheCrudService where TModel : Entity, ICloneable where TEntity : Entity, IDataEntity where TChangeEvent : GenericChangedEntryEvent @@ -60,21 +60,28 @@ public virtual async Task> GetAsync(IList ids, string resp missingIds => GetByIdsNoCache(missingIds, responseGroup), ConfigureCache); - if (!clone) - { - return models; - } + return !clone ? models : models.Select(x => x.CloneTyped()).ToList(); + } - return models - .Select(x => x.CloneTyped()) - .ToList(); + /// + /// Returns a list of model instances for specified IDs without using cache. + /// + /// + /// + /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. + /// + public virtual async Task> GetNoCacheAsync(IList ids, string responseGroup = null, bool clone = true) + { + var models = await GetByIdsNoCache(ids, responseGroup); + + return !clone ? models : models.Select(x => x.CloneTyped()).ToList(); } protected virtual async Task> GetByIdsNoCache(IList ids, string responseGroup) { using var repository = _repositoryFactory(); - // Disable DBContext change tracking for better performance + // Disable DBContext change tracking for better performance repository.DisableChangesTracking(); var entities = await LoadEntities(repository, ids, responseGroup); @@ -341,7 +348,7 @@ protected virtual TEntity FromModel(TModel model, PrimaryKeyResolvingMap keyMap) protected virtual GenericChangedEntryEvent EventFactory(IList> changedEntries) { - return (GenericChangedEntryEvent)typeof(TEvent).GetConstructor(new[] { typeof(IEnumerable>) }).Invoke(new object[] { changedEntries }); + return (GenericChangedEntryEvent)typeof(TEvent).GetConstructor([typeof(IEnumerable>)])?.Invoke([changedEntries]); } } } diff --git a/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs b/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs index 77d39d043ff..ce59eafb586 100644 --- a/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs +++ b/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs @@ -23,7 +23,7 @@ namespace VirtoCommerce.Platform.Data.GenericCrud /// Search result () /// The type of service layer model /// The type of data access layer entity (EF) - public abstract class SearchService : ISearchService + public abstract class SearchService : ISearchService, INoCacheSearchService where TCriteria : SearchCriteriaBase where TResult : GenericSearchResult where TModel : Entity, ICloneable @@ -83,6 +83,30 @@ public virtual async Task SearchAsync(TCriteria criteria, bool clone = return await ProcessSearchResultAsync(result, criteria); } + /// + /// Returns model instances that meet specified criteria without using cache. + /// + /// + /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. + /// + public virtual async Task SearchNoCacheAsync(TCriteria criteria, bool clone = true) + { + ValidateSearchCriteria(criteria); + + var idsResult = await SearchIdsNoCacheAsync(criteria); + + var result = AbstractTypeFactory.TryCreateInstance(); + result.TotalCount = idsResult.TotalCount; + + if (idsResult.Results?.Count > 0) + { + var models = await _crudService.GetNoCacheAsync(idsResult.Results, criteria.ResponseGroup, clone); + result.Results.AddRange(models.OrderBy(x => idsResult.Results.IndexOf(x.Id))); + } + + return await ProcessSearchResultAsync(result, criteria); + } + protected virtual void ValidateSearchCriteria(TCriteria criteria) { From 6e63f1b45d51ac7a6b1da0981d5aceaa861946c6 Mon Sep 17 00:00:00 2001 From: Alexey Shibanov <83034617+alexeyshibanov@users.noreply.github.com> Date: Tue, 30 Jul 2024 06:30:55 +0300 Subject: [PATCH 2/2] Refactor services --- .../Extensions/CrudServiceExtensions.cs | 24 +---- .../Extensions/SearchServiceExtensions.cs | 92 ++++++------------- .../GenericCrud/ICrudService.cs | 13 +++ .../GenericCrud/INoCacheCrudService.cs | 22 ----- .../GenericCrud/INoCacheSearchService.cs | 24 ----- .../GenericCrud/ISearchService.cs | 11 +++ .../GenericCrud/CrudService.cs | 9 +- .../GenericCrud/SearchService.cs | 7 +- 8 files changed, 61 insertions(+), 141 deletions(-) delete mode 100644 src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs delete mode 100644 src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs diff --git a/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs b/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs index 0f21a6cb491..15604f8153b 100644 --- a/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs +++ b/src/VirtoCommerce.Platform.Core/Extensions/CrudServiceExtensions.cs @@ -49,16 +49,7 @@ public static Task> GetByIdsAsync(this ICrudService /// Returns data from the database without using cache. /// - public static Task> GetNoCacheAsync(this ICrudService crudService, IList ids, string responseGroup = null, bool clone = true) - where TModel : Entity - { - return crudService.AsNoCache().GetNoCacheAsync(ids, responseGroup, clone); - } - - /// - /// Returns data from the database without using cache. - /// - public static async Task GetNoCacheAsync(this ICrudService crudService, string id, string responseGroup = null, bool clone = true) + public static async Task GetNoCacheAsync(this ICrudService crudService, string id, string responseGroup = null) where TModel : Entity { if (id is null) @@ -66,19 +57,8 @@ public static async Task GetNoCacheAsync(this ICrudService AsNoCache(this ICrudService crudService) - where TModel : Entity - { - if (crudService is not INoCacheCrudService noCacheService) - { - throw new NotSupportedException("Underlying service does not support no cache search."); - } - - return noCacheService; - } } diff --git a/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs b/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs index eff680dcbb1..8d5f3f7706b 100644 --- a/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs +++ b/src/VirtoCommerce.Platform.Core/Extensions/SearchServiceExtensions.cs @@ -34,6 +34,24 @@ public static async Task> SearchAllAsync + /// Returns data from the database without using cache. + /// + public static async Task> SearchAllNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria) + where TCriteria : SearchCriteriaBase + where TResult : GenericSearchResult + where TModel : IEntity + { + var result = new List(); + + await foreach (var searchResult in searchService.SearchBatchesNoCacheAsync(searchCriteria)) + { + result.AddRange(searchResult.Results); + } + + return result; + } + /// /// Returns data from the cache without cloning. This consumes less memory, but the returned data must not be modified. /// @@ -71,68 +89,26 @@ public static IAsyncEnumerable SearchBatchesNoCloneAsync where TModel : IEntity { - return searchService.SearchBatchesAsync(searchCriteria, clone: false); + return SearchBatchesInternalAsync(searchService, searchCriteria, false); } - public static async IAsyncEnumerable SearchBatchesAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) + public static IAsyncEnumerable SearchBatchesAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) where TCriteria : SearchCriteriaBase where TResult : GenericSearchResult where TModel : IEntity { - int totalCount; - searchCriteria = searchCriteria.CloneTyped(); - - do - { - var searchResult = await searchService.SearchAsync(searchCriteria, clone); - - if (searchCriteria.Take == 0 || - searchResult.Results.Any()) - { - yield return searchResult; - } - - if (searchCriteria.Take == 0) - { - yield break; - } - - totalCount = searchResult.TotalCount; - searchCriteria.Skip += searchCriteria.Take; - } - while (searchCriteria.Skip < totalCount); + return SearchBatchesInternalAsync(searchService, searchCriteria, clone); } - /// - /// Returns data from the database without using cache. - /// - public static Task SearchNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) + public static IAsyncEnumerable SearchBatchesNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria) where TCriteria : SearchCriteriaBase where TResult : GenericSearchResult where TModel : IEntity { - return searchService.AsNoCache().SearchNoCacheAsync(searchCriteria, clone); + return SearchBatchesInternalAsync(searchService, searchCriteria, noCache: true); } - /// - /// Returns data from the database without using cache. - /// - public static async Task> SearchAllNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) - where TCriteria : SearchCriteriaBase - where TResult : GenericSearchResult - where TModel : IEntity - { - var result = new List(); - - await foreach (var searchResult in searchService.SearchBatchesNoCacheAsync(searchCriteria, clone)) - { - result.AddRange(searchResult.Results); - } - - return result; - } - - public static async IAsyncEnumerable SearchBatchesNoCacheAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true) + private static async IAsyncEnumerable SearchBatchesInternalAsync(this ISearchService searchService, TCriteria searchCriteria, bool clone = true, bool noCache = false) where TCriteria : SearchCriteriaBase where TResult : GenericSearchResult where TModel : IEntity @@ -142,9 +118,12 @@ public static async IAsyncEnumerable SearchBatchesNoCacheAsync 0) + if (searchCriteria.Take == 0 || searchResult.Results.Count > 0) { yield return searchResult; } @@ -159,18 +138,5 @@ public static async IAsyncEnumerable SearchBatchesNoCacheAsync AsNoCache(this ISearchService searchService) - where TCriteria : SearchCriteriaBase - where TResult : GenericSearchResult - where TModel : IEntity - { - if (searchService is not INoCacheSearchService noCacheService) - { - throw new NotSupportedException("Underlying service does not support no cache search."); - } - - return noCacheService; - } } } diff --git a/src/VirtoCommerce.Platform.Core/GenericCrud/ICrudService.cs b/src/VirtoCommerce.Platform.Core/GenericCrud/ICrudService.cs index d5eff83ee82..19e62658d92 100644 --- a/src/VirtoCommerce.Platform.Core/GenericCrud/ICrudService.cs +++ b/src/VirtoCommerce.Platform.Core/GenericCrud/ICrudService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using VirtoCommerce.Platform.Core.Common; @@ -20,7 +21,19 @@ public interface ICrudService /// Task> GetAsync(IList ids, string responseGroup = null, bool clone = true); + /// + /// Returns a list of model instances for specified IDs without using cache. + /// + /// + /// + /// + Task> GetNoCacheAsync(IList ids, string responseGroup = null) + { + throw new NotSupportedException("Underlying service does not support no cache search."); + } + Task SaveChangesAsync(IList models); + Task DeleteAsync(IList ids, bool softDelete = false); } } diff --git a/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs b/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs deleted file mode 100644 index 03e09dff311..00000000000 --- a/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheCrudService.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using VirtoCommerce.Platform.Core.Common; - -namespace VirtoCommerce.Platform.Core.GenericCrud; - -/// -/// Generic interface to use with CRUD services without using cache. -/// -/// -public interface INoCacheCrudService - where T : Entity -{ - /// - /// Returns a list of model instances for specified IDs without using cache. - /// - /// - /// - /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. - /// - Task> GetNoCacheAsync(IList ids, string responseGroup = null, bool clone = true); -} diff --git a/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs b/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs deleted file mode 100644 index f2f53693e51..00000000000 --- a/src/VirtoCommerce.Platform.Core/GenericCrud/INoCacheSearchService.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Threading.Tasks; -using VirtoCommerce.Platform.Core.Common; - -namespace VirtoCommerce.Platform.Core.GenericCrud; - -/// -/// Generic interface to use with search services without using cache -/// -/// -/// -/// -public interface INoCacheSearchService - where TCriteria : SearchCriteriaBase - where TResult : GenericSearchResult - where TModel : IEntity -{ - /// - /// Returns model instances that meet specified criteria without using cache. - /// - /// - /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. - /// - Task SearchNoCacheAsync(TCriteria criteria, bool clone = true); -} diff --git a/src/VirtoCommerce.Platform.Core/GenericCrud/ISearchService.cs b/src/VirtoCommerce.Platform.Core/GenericCrud/ISearchService.cs index e24605ca4b3..b4d6189ac5c 100644 --- a/src/VirtoCommerce.Platform.Core/GenericCrud/ISearchService.cs +++ b/src/VirtoCommerce.Platform.Core/GenericCrud/ISearchService.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using VirtoCommerce.Platform.Core.Common; @@ -21,5 +22,15 @@ public interface ISearchService /// If false, returns data from the cache without cloning. This consumes less memory, but the returned data must not be modified. /// Task SearchAsync(TCriteria criteria, bool clone = true); + + /// + /// Returns model instances that meet specified criteria without using cache. + /// + /// + /// + Task SearchNoCacheAsync(TCriteria criteria) + { + throw new NotSupportedException("Underlying service does not support no cache search."); + } } } diff --git a/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs b/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs index 0a56384686d..e78f73b6f38 100644 --- a/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs +++ b/src/VirtoCommerce.Platform.Data/GenericCrud/CrudService.cs @@ -22,7 +22,7 @@ namespace VirtoCommerce.Platform.Data.GenericCrud /// The type of data access layer entity (EF) /// The type of *change event /// The type of *changed event - public abstract class CrudService : ICrudService, INoCacheCrudService + public abstract class CrudService : ICrudService where TModel : Entity, ICloneable where TEntity : Entity, IDataEntity where TChangeEvent : GenericChangedEntryEvent @@ -68,13 +68,10 @@ public virtual async Task> GetAsync(IList ids, string resp /// /// /// - /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. /// - public virtual async Task> GetNoCacheAsync(IList ids, string responseGroup = null, bool clone = true) + public virtual Task> GetNoCacheAsync(IList ids, string responseGroup = null) { - var models = await GetByIdsNoCache(ids, responseGroup); - - return !clone ? models : models.Select(x => x.CloneTyped()).ToList(); + return GetByIdsNoCache(ids, responseGroup); } protected virtual async Task> GetByIdsNoCache(IList ids, string responseGroup) diff --git a/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs b/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs index ce59eafb586..aa1f041e391 100644 --- a/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs +++ b/src/VirtoCommerce.Platform.Data/GenericCrud/SearchService.cs @@ -23,7 +23,7 @@ namespace VirtoCommerce.Platform.Data.GenericCrud /// Search result () /// The type of service layer model /// The type of data access layer entity (EF) - public abstract class SearchService : ISearchService, INoCacheSearchService + public abstract class SearchService : ISearchService where TCriteria : SearchCriteriaBase where TResult : GenericSearchResult where TModel : Entity, ICloneable @@ -87,9 +87,8 @@ public virtual async Task SearchAsync(TCriteria criteria, bool clone = /// Returns model instances that meet specified criteria without using cache. /// /// - /// If false, returns data without cloning. This consumes less memory, but the returned data must not be modified. /// - public virtual async Task SearchNoCacheAsync(TCriteria criteria, bool clone = true) + public virtual async Task SearchNoCacheAsync(TCriteria criteria) { ValidateSearchCriteria(criteria); @@ -100,7 +99,7 @@ public virtual async Task SearchNoCacheAsync(TCriteria criteria, bool c if (idsResult.Results?.Count > 0) { - var models = await _crudService.GetNoCacheAsync(idsResult.Results, criteria.ResponseGroup, clone); + var models = await _crudService.GetNoCacheAsync(idsResult.Results, criteria.ResponseGroup); result.Results.AddRange(models.OrderBy(x => idsResult.Results.IndexOf(x.Id))); }