From 283fe2759968f7f62279198e3554f24f7a3b3b36 Mon Sep 17 00:00:00 2001 From: eglauko Date: Sat, 21 Jun 2025 21:54:06 -0300 Subject: [PATCH 1/9] refatoring --- .../IAllEntities.cs | 1 + .../ICriteria.cs | 179 ++++++++++++++++++ .../ICriteriaOptions.cs | 87 +++++++++ .../IResultList.cs | 2 +- .../ISearch.cs | 10 +- .../ISearchManager.cs | 14 ++ .../ISearchOptions.cs | 47 ----- .../ResultList.cs | 2 +- .../SearchExtensions.cs | 4 +- .../SearchOptions.cs | 29 ++- .../SortingsConverter.cs | 6 +- 11 files changed, 322 insertions(+), 59 deletions(-) create mode 100644 src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs create mode 100644 src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs delete mode 100644 src/RoyalCode.SmartSearch.Abstractions/ISearchOptions.cs diff --git a/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs b/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs index dc45a87..8dd0e8e 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs @@ -12,6 +12,7 @@ /// /// /// The entity type +[Obsolete("Use ICriteria")] public interface IAllEntities where TEntity : class { diff --git a/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs b/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs new file mode 100644 index 0000000..c6f305e --- /dev/null +++ b/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs @@ -0,0 +1,179 @@ +using System.Linq.Expressions; + +namespace RoyalCode.SmartSearch.Abstractions; + +/// +/// +/// Represents a set of criteria for querying entities, allowing the application of multiple filters, +/// sorting, projections, and selection of specific data. The interface +/// is designed for scenarios where entities are to be collected or obtained directly, and these entities +/// will be tracked by the change tracker (e.g., in an ORM context such as Entity Framework). +/// +/// +/// When using methods like , , or , +/// the returned entities are tracked by the change tracker, enabling change detection and persistence. +/// +/// +/// To perform a search where entities are not tracked (i.e., detached from the change tracker), +/// convert the criteria to a search using or . +/// The resulting or will return +/// entities or DTOs that are not tracked by the change tracker. +/// +/// +/// The entity type. +public interface ICriteria : ICriteriaOptions> + where TEntity : class +{ + /// + /// + /// Adds a filter object to the criteria. + /// + /// + /// The search engine must be able to apply this filter, otherwise an exception will be throwed. + /// + /// + /// The filter type. + /// The filter object. + /// The same instance for chaining calls. + ICriteria FilterBy(TFilter filter) + where TFilter : class; + + /// + /// + /// Adds a sorting object to be applied to the criteria. + /// + /// + /// The sorting object. + /// The same instance for chaining calls. + ICriteria OrderBy(ISorting sorting); + + /// + /// + /// Adds multiple sorting objects to be applied to the criteria. + /// + /// + /// The array of sorting objects. + /// The same instance for chaining calls. + ICriteria OrderBy(ISorting[]? sorting); + + /// + /// + /// Convert the criteria to a search. + /// + /// + /// When this method is called, the criteria will be converted to a search, + /// and the entities will not be tracked by a unit of work or a context. + /// + /// + /// new instance of with the same filters. + ISearch AsSearch(); + + /// + /// + /// Requires a Select, adapting the Entity to a DTO. + /// + /// + /// In this method the adaptation of the entity to the DTO will be done by the search engine. + /// + /// + /// When this method is called, the criteria will be converted to a search, + /// and the entities will not be tracked by a unit of work or a context. + /// + /// + /// The Data Transfer Object type. + /// new instance of with the same filters. + ISearch Select() + where TDto : class; + + /// + /// + /// Requires a Select, adapting the Entity to a DTO. + /// + /// + /// In this method, the expression is required to adapt the entity to the DTO. + /// + /// + /// When this method is called, the criteria will be converted to a search, + /// and the entities will not be tracked by a unit of work or a context. + /// + /// + /// The Data Transfer Object type. + /// Expression to adapt the entity to the DTO. + /// new instance of with the same filters. + ISearch Select(Expression> selectExpression) + where TDto : class; + + /// + /// Apply the filters and sorting and get all the entities that meet the criteria. + /// + /// + /// A collection of the entities. + /// + ICollection Collect(); + + /// + /// Apply the filters and sorting and get all the entities that meet the criteria. + /// + /// The cancellation token. + /// + /// A collection of the entities. + /// + Task> CollectAsync(CancellationToken cancellationToken = default); + + /// + /// Apply the filters and sorting and verify if there are any entities that meet the criteria. + /// + /// + /// True if there are entities that meet the criteria, otherwise false. + /// + bool Exists(); + + /// + /// Apply the filters and sorting and verify if there are any entities that meet the criteria. + /// + /// The cancellation token. + /// + /// True if there are entities that meet the criteria, otherwise false. + /// + Task ExistsAsync(CancellationToken cancellationToken = default); + + // Opções First, FirstAsync, Single, SingleAsync, e OrDefault. + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria. + /// + /// + /// The entity or null if there are no entities that meet the criteria. + /// + TEntity? First(); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria. + /// + /// The cancellation token. + /// + /// The entity or null if there are no entities that meet the criteria. + /// + Task FirstAsync(CancellationToken cancellationToken = default); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria, + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + /// + /// The entity that meets the criteria + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + TEntity Single(); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria, + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + /// The cancellation token. + /// + /// The entity that meets the criteria + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + Task SingleAsync(CancellationToken cancellationToken = default); +} diff --git a/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs b/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs new file mode 100644 index 0000000..46c8812 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs @@ -0,0 +1,87 @@ +namespace RoyalCode.SmartSearch.Abstractions; + +/// +/// Options that can be applied into criteria or search components. +/// +/// The search component type. +public interface ICriteriaOptions +{ + /// + /// + /// Defines that the query will be paged and determines the number of items per page. + /// + /// + /// The default value is 10 items per page. + /// + /// + /// When zero (0) is entered, it will not be paged. + /// + /// + /// Items per page. + /// The same instance of the search for chaining calls. + TSearch UsePages(int itemsPerPage = 10); + + /// + /// The number of the page to be searched. + /// + /// The same instance of the search for chaining calls. + TSearch FetchPage(int pageNumber); + + /// + /// + /// Defines the number of records to be skipped in the search. + /// + /// + /// When zero (0) is entered, it will not skip any records. + /// + /// + /// When is informed, this property is ignored. + /// + /// + /// The number of records to skip. + /// The same instance of the search for chaining calls. + TSearch Skip(int skip); + + /// + /// + /// Limits the number of results returned by the search query. + /// + /// + /// When is informed, this property is ignored. + /// + /// + /// The maximum number of results to return. Must be a positive integer. + /// The updated search query with the specified limit applied. + TSearch Take(int take); + + /// + /// + /// Defines the number of records to be skipped and the number of records to be returned in the search. + /// + /// + /// When is informed, this property is ignored. + /// + /// + /// The number of records to skip. + /// The maximum number of results to return. Must be a positive integer. + /// The same instance of the search for chaining calls. + TSearch SkipTake(int skip, int take); + + /// + /// + /// Updates the last record count. + /// + /// + /// Used to not count the records again. + /// + /// + /// The same instance of the search for chaining calls. + TSearch UseLastCount(int lastCount); + + /// + /// Whether to apply record counting. + /// + /// Whether to apply record counting. + /// The same instance of the search for chaining calls. + TSearch UseCount(bool useCount = true); +} diff --git a/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs index 404540b..1729620 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs @@ -37,7 +37,7 @@ public interface IResultList /// The sort objects applied to the search. /// [JsonConverter(typeof(SortingsConverter))] - IEnumerable Sortings { get; } + IReadOnlyList Sortings { get; } /// /// Projections carried out during the research. diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs b/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs index ea27a23..42c3cf8 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs @@ -9,7 +9,7 @@ namespace RoyalCode.SmartSearch.Abstractions; /// /// /// The entity type. -public interface ISearch : ISearchOptions> +public interface ISearch : ICriteriaOptions> where TEntity : class { /// @@ -23,6 +23,7 @@ public interface ISearch : ISearchOptions> /// The filter type. /// The filter object. /// The same instance for chaining calls. + [Obsolete("Use from Criteria")] ISearch FilterBy(TFilter filter) where TFilter : class; @@ -33,6 +34,7 @@ ISearch FilterBy(TFilter filter) /// /// The sorting object. /// The same instance for chaining calls. + [Obsolete("Use from Criteria")] ISearch OrderBy(ISorting sorting); /// @@ -94,7 +96,7 @@ ISearch Select(Expression> selectExpres /// /// The entity type. /// The Data Transfer Object type. -public interface ISearch : ISearchOptions> +public interface ISearch : ICriteriaOptions> where TEntity : class where TDto : class { @@ -112,13 +114,15 @@ public interface ISearch : ISearchOptions> /// The filter type. /// The filter object. /// The same instance for chaining calls. + [Obsolete("Use from Criteria")] ISearch FilterBy(TFilter filter) where TFilter : class; - + /// /// It searches for the entities and returns them in a list of results. /// /// The result list. + [Obsolete("Use from Criteria")] IResultList ToList(); /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs b/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs index 3bff921..fc431c0 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs @@ -10,6 +10,19 @@ /// public interface ISearchManager { + /// + /// + /// Creates a new criteria for the entity. + /// + /// + /// With the criteria, it is possible to apply filters, sorting, projections, + /// and pagination, or collect entities directly from the persistence unit. + /// + /// + /// The entity type. + /// A new instance of . + ICriteria Criteria() where TEntity : class; + /// /// /// Creates a new search for the entity. @@ -23,6 +36,7 @@ public interface ISearchManager /// /// If entity is not part of the persistence unit or there is no search component for it. /// + [Obsolete("Use the Criteria method to get a ICriteria instance instead, and then get a Search orSearch.")] ISearch Search() where TEntity : class; /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISearchOptions.cs b/src/RoyalCode.SmartSearch.Abstractions/ISearchOptions.cs deleted file mode 100644 index 57463c8..0000000 --- a/src/RoyalCode.SmartSearch.Abstractions/ISearchOptions.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace RoyalCode.SmartSearch.Abstractions; - -/// -/// Options that can be applied into searches. -/// -/// The search component type. -public interface ISearchOptions -{ - /// - /// - /// Defines that the query will be paged and determines the number of items per page. - /// - /// - /// The default value is 10 items per page. - /// - /// - /// When zero (0) is entered, it will not be paged. - /// - /// - /// Items per page. - /// The same instance of the search for chaining calls. - TSearch UsePages(int itemsPerPage = 10); - - /// - /// The number of the page to be searched. - /// - /// The same instance of the search for chaining calls. - TSearch FetchPage(int pageNumber); - - /// - /// - /// Updates the last record count. - /// - /// - /// Used to not count the records again. - /// - /// - /// The same instance of the search for chaining calls. - TSearch UseLastCount(int lastCount); - - /// - /// Whether to apply record counting. - /// - /// Whether to apply record counting. - /// The same instance of the search for chaining calls. - TSearch UseCount(bool useCount = true); -} diff --git a/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs index 9f82cef..2e5efee 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs @@ -26,7 +26,7 @@ public class ResultList : IResultList /// [JsonConverter(typeof(SortingsConverter))] - public IEnumerable Sortings { get; init; } = null!; + public IReadOnlyList Sortings { get; init; } = null!; /// public Dictionary Projections { get; init; } = null!; diff --git a/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs b/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs index 568f19d..a154a92 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs @@ -4,14 +4,14 @@ namespace RoyalCode.SmartSearch.Abstractions; /// /// -/// Extensions methods for , +/// Extensions methods for , /// and . /// /// public static class SearchExtensions { /// - /// Applies the to the . + /// Applies the to the . /// /// The search object type. /// The search. diff --git a/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs b/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs index 712abe5..3b0e893 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs @@ -15,6 +15,11 @@ public sealed class SearchOptions { private List? sortings; + /// + /// The number of the page to be searched. + /// + public int? Page { get; set; } + /// /// /// Defines that the query will be paged and determines the number of items per page. @@ -29,9 +34,29 @@ public sealed class SearchOptions public int? ItemsPerPage { get; set; } /// - /// The number of the page to be searched. + /// + /// Defines the number of records to be skipped in the search. + /// When zero (0) is entered, it will not skip any records. + /// + /// + /// When is informed, this property is ignored. + /// /// - public int? Page { get; set; } + public int? Skip { get; set; } + + /// + /// + /// Defines the number of records to be returned in the search. + /// + /// + /// This property must be use with to define the number of records to be returned + /// after skipping the defined number of records. + /// + /// + /// When is informed, this property is ignored. + /// + /// + public int? Take { get; set; } /// /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs b/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs index 5f08b72..e921a57 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs @@ -3,14 +3,14 @@ namespace RoyalCode.SmartSearch.Abstractions; -internal sealed class SortingsConverter : JsonConverter> +internal sealed class SortingsConverter : JsonConverter> { - public override IEnumerable? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override IReadOnlyList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize>(ref reader, options); } - public override void Write(Utf8JsonWriter writer, IEnumerable value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, IReadOnlyList value, JsonSerializerOptions options) { JsonSerializer.Serialize(writer, value, options); } From 9fd200c6dd88fb477fc079ec0de7e153f899d1a8 Mon Sep 17 00:00:00 2001 From: eglauko Date: Sun, 22 Jun 2025 22:36:59 -0300 Subject: [PATCH 2/9] refactoring --- .../AllEntitiesExtensions.cs | 180 ---------------- .../AsyncResultList.cs | 10 +- .../CriterionAttribute.cs | 2 +- .../CriterionOperator.cs | 2 +- .../IAllEntities.cs | 199 ------------------ .../IAsyncResultList.cs | 2 +- .../ICriteria.cs | 14 +- .../ICriteriaOptions.cs | 5 +- .../IResultList.cs | 12 +- .../ISearch.cs | 126 ++++++----- .../ISearchManager.cs | 38 +--- .../ISorting.cs | 2 +- .../ResultList.cs | 10 +- .../RoyalCode.SmartSearch.Abstractions.csproj | 1 + .../SearchExtensions.cs | 37 ++-- .../SearchOptions.cs | 6 +- .../Sorting.cs | 2 +- .../SortingsConverter.cs | 2 +- .../Defaults/Criteria.cs | 154 ++++++++++++++ .../CriteriaOptions.cs} | 77 ++++--- .../Defaults/Search.cs | 115 ++++++++++ .../EntityFilter.cs} | 8 +- .../Filtering/IFilter.cs | 13 ++ .../ISpecifier.cs} | 8 +- .../IFilterSpecifier.cs | 17 -- .../ISearchFilter.cs | 13 -- .../Mappings/ISearchSelect.cs | 25 +++ .../Mappings/ISearchSelector.cs | 38 ++++ .../Mappings/SearchSelect.cs | 55 +++++ .../Pipeline/AllEntities.cs | 173 --------------- .../Pipeline/IAllEntitiesPipeline.cs | 173 --------------- .../Pipeline/IPipelineFactory.cs | 38 ---- .../Pipeline/ISearchPipeline.cs | 39 ---- .../Pipeline/Search.cs | 93 -------- .../RoyalCode.SmartSearch.Core.csproj | 1 + src/RoyalCode.SmartSearch.Core/SearchBase.cs | 139 ------------ .../SearchSelect.cs | 23 -- .../Services/ICriteriaPerformer.cs | 18 ++ .../Services/IPreparedQuery.cs | 32 +++ .../AllEntitiesPipeline.cs | 34 +-- .../SearchPipeline.cs | 16 +- .../SearchPipelineBase.cs | 4 +- .../Filter/SpecifierHandler.cs | 8 +- .../IQueryableProvider.cs | 6 - src/RoyalCode.SmartSearch.Linq/IRemovable.cs | 1 + src/RoyalCode.SmartSearch.Linq/ISorter.cs | 1 - src/RoyalCode.SmartSearch.Linq/ISpecifier.cs | 3 +- 47 files changed, 682 insertions(+), 1293 deletions(-) delete mode 100644 src/RoyalCode.SmartSearch.Abstractions/AllEntitiesExtensions.cs delete mode 100644 src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs create mode 100644 src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs rename src/RoyalCode.SmartSearch.Core/{SearchCriteria.cs => Defaults/CriteriaOptions.cs} (62%) create mode 100644 src/RoyalCode.SmartSearch.Core/Defaults/Search.cs rename src/RoyalCode.SmartSearch.Core/{SearchFilter.cs => Filtering/EntityFilter.cs} (57%) create mode 100644 src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs rename src/RoyalCode.SmartSearch.Core/{ISpecifierHandler.cs => Filtering/ISpecifier.cs} (68%) delete mode 100644 src/RoyalCode.SmartSearch.Core/IFilterSpecifier.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/ISearchFilter.cs create mode 100644 src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs create mode 100644 src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs create mode 100644 src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/Pipeline/AllEntities.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/Pipeline/IAllEntitiesPipeline.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/Pipeline/IPipelineFactory.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/Pipeline/ISearchPipeline.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/Pipeline/Search.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/SearchBase.cs delete mode 100644 src/RoyalCode.SmartSearch.Core/SearchSelect.cs create mode 100644 src/RoyalCode.SmartSearch.Core/Services/ICriteriaPerformer.cs create mode 100644 src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs diff --git a/src/RoyalCode.SmartSearch.Abstractions/AllEntitiesExtensions.cs b/src/RoyalCode.SmartSearch.Abstractions/AllEntitiesExtensions.cs deleted file mode 100644 index 269a2f1..0000000 --- a/src/RoyalCode.SmartSearch.Abstractions/AllEntitiesExtensions.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System.Linq.Expressions; - -namespace RoyalCode.SmartSearch.Abstractions; - -/// -/// Extension methods for . -/// -public static class AllEntitiesExtensions -{ - /// - /// Apply the filters and sorting and update all entities that meet the criteria. - /// - /// - /// - /// - /// - /// - public static void UpdateAll( - this IAllEntities allEntities, - ICollection data, - Action updateAction) - where TEntity : class - where TData : class - { - UpdateAllWithHelper.Execute(allEntities, data, updateAction); - } - - /// - /// Apply the filters and sorting and update all entities that meet the criteria. - /// - /// - /// - /// - /// - /// - /// - public static Task UpdateAllWith( - this IAllEntities allEntities, - ICollection data, - Action updateAction) - where TEntity : class - where TData : class - { - return UpdateAllWithHelper.ExecuteAsync(allEntities, data, updateAction); - } - - private static class UpdateAllWithHelper - where TEntity : class - where TData : class - { - private static Action, ICollection, Action>? execute; - private static Func, ICollection, Action, Task>? executeAsync; - - public static void Execute( - IAllEntities allEntities, - ICollection data, - Action updateAction) - { - execute ??= Create(); - execute(allEntities, data, updateAction); - } - - public static Task ExecuteAsync( - IAllEntities allEntities, - ICollection data, - Action updateAction) - { - executeAsync ??= CreateAsync(); - return executeAsync(allEntities, data, updateAction); - } - - private static Action, ICollection, Action> Create() - { - // get the property Id from TEntity - var idProperty = typeof(TEntity).GetProperty("Id") - ?? throw new InvalidOperationException("The entity does not have a property Id."); - - // get the property Id from TData - var dataIdProperty = typeof(TData).GetProperty("Id") - ?? throw new InvalidOperationException("The data does not have a property Id."); - - // validate types of the id properties - if (idProperty.PropertyType != dataIdProperty.PropertyType) - throw new InvalidOperationException("The type of the id properties are different."); - - // create a function expression to get the id from TEntity - var entityParameter = Expression.Parameter(typeof(TEntity), "entity"); - var lambdaType = typeof(Func<,>).MakeGenericType(typeof(TEntity), idProperty.PropertyType); - var entityLambda = Expression.Lambda(lambdaType, - Expression.Property(entityParameter, idProperty), - entityParameter); - - // create a function expression to get the id from TData - var dataParameter = Expression.Parameter(typeof(TData), "data"); - var dataLambda = Expression.Lambda(lambdaType, - Expression.Property(dataParameter, dataIdProperty), - dataParameter); - - // create expression paramters for allEntities, data, and updateAction - var allEntitiesParameter = Expression.Parameter(typeof(IAllEntities), "allEntities"); - dataParameter = Expression.Parameter(typeof(ICollection), "data"); - var updateActionParameter = Expression.Parameter(typeof(Action), "updateAction"); - - // create a call to the UpdateAll method - var updateAllMethod = typeof(IAllEntities).GetMethods() - .First(m => m.Name == nameof(IAllEntities.UpdateAll) && m.GetParameters().Length == 4); - var updateAllCall = Expression.Call( - allEntitiesParameter, - updateAllMethod, - dataParameter, - entityLambda, - dataLambda, - updateActionParameter); - - // create a lambda expression - var lambda = Expression.Lambda, ICollection, Action>>( - updateAllCall, - allEntitiesParameter, - dataParameter, - updateActionParameter); - - // compile the lambda expression - return lambda.Compile(); - } - - private static Func,ICollection,Action,Task> CreateAsync() - { - // get the property Id from TEntity - var idProperty = typeof(TEntity).GetProperty("Id") - ?? throw new InvalidOperationException("The entity does not have a property Id."); - - // get the property Id from TData - var dataIdProperty = typeof(TData).GetProperty("Id") - ?? throw new InvalidOperationException("The data does not have a property Id."); - - // validate types of the id properties - if (idProperty.PropertyType != dataIdProperty.PropertyType) - throw new InvalidOperationException("The type of the id properties are different."); - - // create a function expression to get the id from TEntity - var entityParameter = Expression.Parameter(typeof(TEntity), "entity"); - var lambdaType = typeof(Func<,>).MakeGenericType(typeof(TEntity), idProperty.PropertyType); - var entityLambda = Expression.Lambda(lambdaType, - Expression.Property(entityParameter, idProperty), - entityParameter); - - // create a function expression to get the id from TData - var dataParameter = Expression.Parameter(typeof(TData), "data"); - var dataLambda = Expression.Lambda(lambdaType, - Expression.Property(dataParameter, dataIdProperty), - dataParameter); - - // create expression paramters for allEntities, data, and updateAction - var allEntitiesParameter = Expression.Parameter(typeof(IAllEntities), "allEntities"); - dataParameter = Expression.Parameter(typeof(ICollection), "data"); - var updateActionParameter = Expression.Parameter(typeof(Action), "updateAction"); - - // create a call to the UpdateAllAsync method - var updateAllMethod = typeof(IAllEntities).GetMethods() - .First(m => m.Name == nameof(IAllEntities.UpdateAllAsync) && m.GetParameters().Length == 5); - var updateAllCall = Expression.Call( - allEntitiesParameter, - updateAllMethod, - dataParameter, - entityLambda, - dataLambda, - updateActionParameter); - - // create a lambda expression - var lambda = Expression.Lambda,ICollection,Action,Task>>( - updateAllCall, - allEntitiesParameter, - dataParameter, - updateActionParameter); - - // compile the lambda expression - return lambda.Compile(); - } - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs index 14172dd..9c971d9 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -22,9 +22,15 @@ public sealed class AsyncResultList : IAsyncResultList /// public int Pages { get; init; } + /// + public int Skipped { get; } + + /// + public int Taken { get; } + /// [JsonConverter(typeof(SortingsConverter))] - public IEnumerable Sortings { get; init; } = null!; + public IReadOnlyList Sortings { get; init; } = null!; /// public Dictionary Projections { get; init; } = null!; diff --git a/src/RoyalCode.SmartSearch.Abstractions/CriterionAttribute.cs b/src/RoyalCode.SmartSearch.Abstractions/CriterionAttribute.cs index e93c029..2f8f44b 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/CriterionAttribute.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/CriterionAttribute.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/CriterionOperator.cs b/src/RoyalCode.SmartSearch.Abstractions/CriterionOperator.cs index a37dc7c..b8125b6 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/CriterionOperator.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/CriterionOperator.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs b/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs deleted file mode 100644 index 8dd0e8e..0000000 --- a/src/RoyalCode.SmartSearch.Abstractions/IAllEntities.cs +++ /dev/null @@ -1,199 +0,0 @@ -namespace RoyalCode.SmartSearch.Abstractions; - -/// -/// -/// A search that returns all entities with purpose to edit them. -/// -/// -/// Filters and sorting can be applied, but the search engine must be able to apply them. -/// -/// -/// When used with a unit of work, all changes made to the entities must be saved. -/// -/// -/// The entity type -[Obsolete("Use ICriteria")] -public interface IAllEntities - where TEntity : class -{ - /// - /// - /// Adds a filter object to the search. - /// - /// - /// The search engine must be able to apply this filter, otherwise an exception will be throwed. - /// - /// - /// The filter type. - /// The filter object. - /// The same instance for chaining calls. - IAllEntities FilterBy(TFilter filter) - where TFilter : class; - - /// - /// - /// Adds a sorting object to be applied to the search. - /// - /// - /// The sorting object. - /// The same instance for chaining calls. - IAllEntities OrderBy(ISorting sorting); - - /// - /// Apply the filters and sorting and get all the entities that meet the criteria. - /// - /// - /// A collection of the entities. - /// - ICollection Collect(); - - /// - /// Apply the filters and sorting and get all the entities that meet the criteria. - /// - /// The cancellation token. - /// - /// A collection of the entities. - /// - Task> CollectAsync(CancellationToken cancellationToken = default); - - /// - /// Apply the filters and sorting and verify if there are any entities that meet the criteria. - /// - /// - /// True if there are entities that meet the criteria, otherwise false. - /// - bool Exists(); - - /// - /// Apply the filters and sorting and verify if there are any entities that meet the criteria. - /// - /// The cancellation token. - /// - /// True if there are entities that meet the criteria, otherwise false. - /// - Task ExistsAsync(CancellationToken cancellationToken = default); - - // Opções First, FirstAsync, Single, SingleAsync, e OrDefault. - - /// - /// Apply the filters and sorting and get the first entity that meets the criteria. - /// - /// - /// The entity or null if there are no entities that meet the criteria. - /// - TEntity? First(); - - /// - /// Apply the filters and sorting and get the first entity that meets the criteria. - /// - /// The cancellation token. - /// - /// The entity or null if there are no entities that meet the criteria. - /// - Task FirstAsync(CancellationToken cancellationToken = default); - - /// - /// Apply the filters and sorting and get the first entity that meets the criteria, - /// or throw an exception if there are no entities that meet the criteria or more than one. - /// - /// - /// The entity that meets the criteria - /// or throw an exception if there are no entities that meet the criteria or more than one. - /// - TEntity Single(); - - /// - /// Apply the filters and sorting and get the first entity that meets the criteria, - /// or throw an exception if there are no entities that meet the criteria or more than one. - /// - /// The cancellation token. - /// - /// The entity that meets the criteria - /// or throw an exception if there are no entities that meet the criteria or more than one. - /// - Task SingleAsync(CancellationToken cancellationToken = default); - - /// - /// Apply the filters and sorting and delete all the entities that meet the criteria. - /// - void DeleteAll(); - - /// - /// Apply the filters and sorting and delete all the entities that meet the criteria. - /// - /// The cancellation token. - /// A task representing the asynchronous operation. - Task DeleteAllAsync(CancellationToken cancellationToken = default); - - /// - /// Apply the filters and sorting and update all the entities that meet the criteria. - /// - /// The action to update the entities. - void UpdateAll(Action updateAction); - - /// - /// Apply the filters and sorting and update all entities that meet the criteria. - /// - /// The data used to update the entities. - /// The action to update the entities. - /// The type of the data used to update the entities. - void UpdateAll(TData data, Action updateAction); - - /// - /// Apply the filters and sorting and update all entities that meet the criteria. - /// - /// A collection of data used to update the entities. - /// The function to get the entity id. - /// The function to get the data id. - /// The action to update the entities. - /// The type of the data used to update the entities. - /// The type of the id. - void UpdateAll( - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction) - where TData : class; - - /// - /// Apply the filters and sorting and update all the entities that meet the criteria. - /// - /// The action to update the entities. - /// The cancellation token. - /// A task representing the asynchronous operation. - Task UpdateAllAsync( - Action updateAction, - CancellationToken cancellationToken = default); - - /// - /// Apply the filters and sorting and update all entities that meet the criteria. - /// - /// The data used to update the entities. - /// The action to update the entities. - /// The cancellation token. - /// The type of the data used to update the entities. - /// A task representing the asynchronous operation. - Task UpdateAllAsync( - TData data, - Action updateAction, - CancellationToken cancellationToken = default); - - /// - /// Apply the filters and sorting and update all entities that meet the criteria. - /// - /// The collection of data used to update the entities. - /// The function to get the entity id. - /// The function to get the data id. - /// The action to update the entities. - /// The cancellation token. - /// The type of the data used to update the entities. - /// The type of the id. - /// A task that represents the asynchronous operation. - Task UpdateAllAsync( - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction, - CancellationToken cancellationToken = default) - where TData : class; -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Abstractions/IAsyncResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/IAsyncResultList.cs index 65e7d9b..20e66bf 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/IAsyncResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/IAsyncResultList.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// Component interface for async listing the result of a search. diff --git a/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs b/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs index c6f305e..343ce4a 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -10,7 +10,7 @@ namespace RoyalCode.SmartSearch.Abstractions; /// will be tracked by the change tracker (e.g., in an ORM context such as Entity Framework). /// /// -/// When using methods like , , or , +/// When using methods like , , or , /// the returned entities are tracked by the change tracker, enabling change detection and persistence. /// /// @@ -109,7 +109,7 @@ ISearch Select(Expression> selectExpres /// /// A collection of the entities. /// - ICollection Collect(); + IReadOnlyList Collect(); /// /// Apply the filters and sorting and get all the entities that meet the criteria. @@ -118,7 +118,7 @@ ISearch Select(Expression> selectExpres /// /// A collection of the entities. /// - Task> CollectAsync(CancellationToken cancellationToken = default); + Task> CollectAsync(CancellationToken cancellationToken = default); /// /// Apply the filters and sorting and verify if there are any entities that meet the criteria. @@ -137,15 +137,13 @@ ISearch Select(Expression> selectExpres /// Task ExistsAsync(CancellationToken cancellationToken = default); - // Opções First, FirstAsync, Single, SingleAsync, e OrDefault. - /// /// Apply the filters and sorting and get the first entity that meets the criteria. /// /// /// The entity or null if there are no entities that meet the criteria. /// - TEntity? First(); + TEntity? FirstOrDefault(); /// /// Apply the filters and sorting and get the first entity that meets the criteria. @@ -154,7 +152,7 @@ ISearch Select(Expression> selectExpres /// /// The entity or null if there are no entities that meet the criteria. /// - Task FirstAsync(CancellationToken cancellationToken = default); + Task FirstDefaultAsync(CancellationToken cancellationToken = default); /// /// Apply the filters and sorting and get the first entity that meets the criteria, diff --git a/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs b/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs index 46c8812..8079046 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ICriteriaOptions.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// Options that can be applied into criteria or search components. @@ -18,8 +18,9 @@ public interface ICriteriaOptions /// /// /// Items per page. + /// The page number. /// The same instance of the search for chaining calls. - TSearch UsePages(int itemsPerPage = 10); + TSearch UsePages(int itemsPerPage = 10, int pageNumber = 1); /// /// The number of the page to be searched. diff --git a/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs index 1729620..924f1e2 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -33,6 +33,16 @@ public interface IResultList /// int Pages { get; } + /// + /// Number of items skipped in the result set. + /// + int Skipped { get; } + + /// + /// Number of items taken from the result set. + /// + int Taken { get; } + /// /// The sort objects applied to the search. /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs b/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs index 42c3cf8..6006faf 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -9,34 +9,9 @@ namespace RoyalCode.SmartSearch.Abstractions; /// /// /// The entity type. -public interface ISearch : ICriteriaOptions> +public interface ISearch where TEntity : class { - /// - /// - /// Adds a filter object to the search. - /// - /// - /// The search engine must be able to apply this filter, otherwise an exception will be throwed. - /// - /// - /// The filter type. - /// The filter object. - /// The same instance for chaining calls. - [Obsolete("Use from Criteria")] - ISearch FilterBy(TFilter filter) - where TFilter : class; - - /// - /// - /// Adds a sorting object to be applied to the search. - /// - /// - /// The sorting object. - /// The same instance for chaining calls. - [Obsolete("Use from Criteria")] - ISearch OrderBy(ISorting sorting); - /// /// /// Requires a Select, adapting the Entity to a DTO. @@ -83,6 +58,44 @@ ISearch Select(Expression> selectExpres /// The task cancellation token. /// A task to wait for the async list of results. Task> ToAsyncListAsync(CancellationToken token); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria. + /// + /// + /// The entity or null if there are no entities that meet the criteria. + /// + TEntity? FirstOrDefault(); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria. + /// + /// The cancellation token. + /// + /// The entity or null if there are no entities that meet the criteria. + /// + Task FirstDefaultAsync(CancellationToken cancellationToken = default); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria, + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + /// + /// The entity that meets the criteria + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + TEntity Single(); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria, + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + /// The cancellation token. + /// + /// The entity that meets the criteria + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + Task SingleAsync(CancellationToken cancellationToken = default); } /// @@ -96,33 +109,14 @@ ISearch Select(Expression> selectExpres /// /// The entity type. /// The Data Transfer Object type. -public interface ISearch : ICriteriaOptions> +public interface ISearch where TEntity : class where TDto : class { - /// - /// - /// Adds a filter object to the search. - /// - /// - /// The search engine must be able to apply this filter, otherwise an exception will be throwed. - /// - /// - /// This filter will be applied against the query type. - /// - /// - /// The filter type. - /// The filter object. - /// The same instance for chaining calls. - [Obsolete("Use from Criteria")] - ISearch FilterBy(TFilter filter) - where TFilter : class; - /// /// It searches for the entities and returns them in a list of results. /// /// The result list. - [Obsolete("Use from Criteria")] IResultList ToList(); /// @@ -138,4 +132,42 @@ ISearch FilterBy(TFilter filter) /// The task cancellation token. /// A task to wait for the async list of results. Task> ToAsyncListAsync(CancellationToken token); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria. + /// + /// + /// The entity or null if there are no entities that meet the criteria. + /// + TDto? FirstOrDefault(); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria. + /// + /// The cancellation token. + /// + /// The entity or null if there are no entities that meet the criteria. + /// + Task FirstDefaultAsync(CancellationToken cancellationToken = default); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria, + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + /// + /// The entity that meets the criteria + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + TDto Single(); + + /// + /// Apply the filters and sorting and get the first entity that meets the criteria, + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + /// The cancellation token. + /// + /// The entity that meets the criteria + /// or throw an exception if there are no entities that meet the criteria or more than one. + /// + Task SingleAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs b/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs index fc431c0..1d7dbac 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ISearchManager.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -22,41 +22,5 @@ public interface ISearchManager /// The entity type. /// A new instance of . ICriteria Criteria() where TEntity : class; - - /// - /// - /// Creates a new search for the entity. - /// - /// - /// There must be a search component for the persistence unit, otherwise an exception will be thrown. - /// - /// - /// The entity type. - /// A new instance of . - /// - /// If entity is not part of the persistence unit or there is no search component for it. - /// - [Obsolete("Use the Criteria method to get a ICriteria instance instead, and then get a Search orSearch.")] - ISearch Search() where TEntity : class; - - /// - /// - /// Gets all entities of the persistence unit, where it is possible to add filters and sorters. - /// - /// - /// The purpose of is to query entities for update or delete. - /// - /// - /// When used with a unit of work, all changes made to the entities must be saved. - /// - /// - /// The entity type. - /// - /// A new instance of . - /// - /// - /// If entity is not part of the persistence unit or there is no search component for it. - /// - IAllEntities All() where TEntity : class; } diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISorting.cs b/src/RoyalCode.SmartSearch.Abstractions/ISorting.cs index 286adab..78b8855 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ISorting.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ISorting.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// Component that contains information about the sorting applied to a search/query. diff --git a/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs index 2e5efee..05d093e 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -24,6 +24,12 @@ public class ResultList : IResultList /// public int Pages { get; init; } + /// + public int Skipped { get; } + + /// + public int Taken { get; } + /// [JsonConverter(typeof(SortingsConverter))] public IReadOnlyList Sortings { get; init; } = null!; @@ -33,7 +39,7 @@ public class ResultList : IResultList /// public IReadOnlyList Items { get; init; } = null!; - + /// public virtual T GetProjection(string name, T? defaultValue = default) { diff --git a/src/RoyalCode.SmartSearch.Abstractions/RoyalCode.SmartSearch.Abstractions.csproj b/src/RoyalCode.SmartSearch.Abstractions/RoyalCode.SmartSearch.Abstractions.csproj index 3e09872..1c91913 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/RoyalCode.SmartSearch.Abstractions.csproj +++ b/src/RoyalCode.SmartSearch.Abstractions/RoyalCode.SmartSearch.Abstractions.csproj @@ -4,6 +4,7 @@ $(LibTargets) + RoyalCode.SmartSearch diff --git a/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs b/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs index a154a92..bbde4c4 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/SearchExtensions.cs @@ -1,6 +1,6 @@ // Ignore Spelling: sortings -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// @@ -14,43 +14,32 @@ public static class SearchExtensions /// Applies the to the . /// /// The search object type. - /// The search. + /// The criteria. /// The options. /// The search with the options applied. - public static ISearch WithOptions(this ISearch search, SearchOptions options) + public static ICriteria WithOptions(this ICriteria criteria, SearchOptions options) where T : class { if (options.ItemsPerPage.HasValue) - search = search.UsePages(options.ItemsPerPage.Value); + criteria = criteria.UsePages(options.ItemsPerPage.Value); if (options.Page.HasValue) - search = search.FetchPage(options.Page.Value); + criteria = criteria.FetchPage(options.Page.Value); if (options.LastCount.HasValue) - search = search.UseLastCount(options.LastCount.Value); + criteria = criteria.UseLastCount(options.LastCount.Value); if (options.Count.HasValue) - search = search.UseCount(options.Count.Value); + criteria = criteria.UseCount(options.Count.Value); - OrderBy(search, options.Sortings); + if (options.Skip.HasValue) + criteria = criteria.Skip(options.Skip.Value); - return search; - } + if (options.Take.HasValue) + criteria = criteria.Take(options.Take.Value); - /// - /// Applies a set of sorting to the search. - /// - /// The search object type. - /// The search. - /// The sortings. - /// The search with the sortings applied. - public static ISearch OrderBy(this ISearch search, ISorting[]? sortings) - where T : class - { - if (sortings is not null) - foreach (var sorting in sortings) - search.OrderBy(sorting); + criteria.OrderBy(options.Sortings); - return search; + return criteria; } } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs b/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs index 3b0e893..9232607 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/SearchOptions.cs @@ -1,14 +1,14 @@ // Ignore Spelling: sortings -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// -/// A class that contains the options for a search. +/// A class that contains the options for a criteria and search. /// It is used to define the search parameters. /// /// -/// It is design to retrieve the options from a query string and apply them to the search. +/// It is designed to retrieve the options from a query string and apply them to the search. /// /// public sealed class SearchOptions diff --git a/src/RoyalCode.SmartSearch.Abstractions/Sorting.cs b/src/RoyalCode.SmartSearch.Abstractions/Sorting.cs index 0ad60f4..c65c204 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/Sorting.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/Sorting.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.Text.Json; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; /// /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs b/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs index e921a57..0021a34 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/SortingsConverter.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; using System.Text.Json; -namespace RoyalCode.SmartSearch.Abstractions; +namespace RoyalCode.SmartSearch; internal sealed class SortingsConverter : JsonConverter> { diff --git a/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs b/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs new file mode 100644 index 0000000..70d47bb --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs @@ -0,0 +1,154 @@ + +using RoyalCode.SmartSearch.Mappings; +using RoyalCode.SmartSearch.Services; +using System.Linq.Expressions; + +namespace RoyalCode.SmartSearch.Defaults; + +/// +/// Default implementation of the interface. +/// +/// The entity type. +public class Criteria : ICriteria + where TEntity : class +{ + private readonly ICriteriaPerformer performer; + + /// + /// + /// Creates a new with the service to perform the criteria. + /// + /// + /// + /// A service capable of accessing the database and running a query using the Criteria options. + /// + public Criteria(ICriteriaPerformer performer) + { + this.performer = performer; + } + + private readonly CriteriaOptions options = new(); + + /// + public ICriteria UsePages(int itemsPerPage = 10, int pageNumber = 1) + { + options.ItemsPerPage = itemsPerPage; + options.Page = pageNumber; + return this; + } + + /// + public ICriteria FetchPage(int pageNumber) + { + options.Page = pageNumber; + return this; + } + + /// + public ICriteria Skip(int skip) + { + options.Skip = skip; + return this; + } + + /// + public ICriteria Take(int take) + { + options.Take = take; + return this; + } + + /// + public ICriteria SkipTake(int skip, int take) + { + options.Skip = skip; + options.Take = take; + return this; + } + + /// + public ICriteria UseLastCount(int lastCount) + { + options.LastCount = lastCount; + return this; + } + + /// + public ICriteria UseCount(bool useCount = true) + { + options.UseCount = useCount; + return this; + } + + /// + public ICriteria FilterBy(TFilter filter) + where TFilter : class + { + options.AddFilter(filter); + return this; + } + + /// + public ICriteria OrderBy(ISorting sorting) + { + options.AddSorting(sorting); + return this; + } + + /// + public ICriteria OrderBy(ISorting[]? sorting) + { + options.AddSorting(sorting); + return this; + } + + /// + public ISearch AsSearch() + { + return new Search(performer, options); + } + + /// + public ISearch Select() + where TDto : class + { + var select = new SearchSelect(); + return new Search(performer, options, select); + } + + /// + public ISearch Select(Expression> selectExpression) + where TDto : class + { + var select = new SearchSelect(selectExpression); + return new Search(performer, options, select); + } + + /// + public IReadOnlyList Collect() => performer.Prepare(options).ToList(); + + /// + public async Task> CollectAsync(CancellationToken cancellationToken = default) + => await performer.Prepare(options).ToListAsync(cancellationToken); + + /// + public bool Exists() => performer.Prepare(options).Exists(); + + /// + public async Task ExistsAsync(CancellationToken cancellationToken = default) + => await performer.Prepare(options).ExistsAsync(cancellationToken); + + /// + public TEntity? FirstOrDefault() => performer.Prepare(options).FirstOrDefault(); + + /// + public async Task FirstDefaultAsync(CancellationToken cancellationToken = default) + => await performer.Prepare(options).FirstOrDefaultAsync(cancellationToken); + + /// + public TEntity Single() => performer.Prepare(options).Single(); + + /// + public async Task SingleAsync(CancellationToken cancellationToken = default) + => await performer.Prepare(options).SingleAsync(cancellationToken); +} diff --git a/src/RoyalCode.SmartSearch.Core/SearchCriteria.cs b/src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs similarity index 62% rename from src/RoyalCode.SmartSearch.Core/SearchCriteria.cs rename to src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs index bacf24f..1ff2d34 100644 --- a/src/RoyalCode.SmartSearch.Core/SearchCriteria.cs +++ b/src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs @@ -1,33 +1,26 @@ -// Ignore Spelling: Sortings +using RoyalCode.SmartSearch.Filtering; -using System.Linq.Expressions; -using RoyalCode.SmartSearch.Abstractions; - -namespace RoyalCode.SmartSearch.Core; +namespace RoyalCode.SmartSearch.Defaults; /// -/// The criteria for performing the search. +/// The criteria options for performing the search. /// -public sealed class SearchCriteria +public sealed class CriteriaOptions { - private List? filters; + private List? filters; private List? sortings; + private bool trackingEnabled = true; /// /// Get all filters. /// - public IReadOnlyList Filters => filters ?? []; + public IReadOnlyList Filters => filters ?? []; /// /// Get all sortings. /// public IReadOnlyList Sortings => sortings ?? []; - /// - /// Information about the select expression. - /// - public SearchSelect? Select { get; private set; } - /// /// /// Defines that the query will be paged and determines the number of items per page. @@ -46,6 +39,16 @@ public sealed class SearchCriteria /// public int Page { get; set; } + /// + /// The number of records to be skipped in the search. + /// + public int Skip { get; set; } + + /// + /// The number of records to be returned in the search. + /// + public int Take { get; set; } + /// /// /// Updates the last record count. @@ -61,6 +64,14 @@ public sealed class SearchCriteria /// public bool UseCount { get; set; } = Defaults.DefaultUseCount; + /// + /// Disables entity tracking for the query, which can improve performance for read-only operations. + /// + public void NoTracking() + { + trackingEnabled = false; + } + /// /// Adds a new filter to specify the search. /// @@ -69,7 +80,7 @@ public void AddFilter(TFilter filter) where TFilter : class { filters ??= []; - filters.Add(new SearchFilter(filter)); + filters.Add(new EntityFilter(filter)); } /// @@ -83,23 +94,21 @@ public void AddSorting(ISorting sorting) } /// - /// Set the select expression. + /// Adds a sorting definition. /// - /// The select expression. - /// The query entity type. - /// The select type. - /// If expression is null. - public void SetSelectExpression(Expression> selectExpression) + /// The sorting definitions. + public void AddSorting(IEnumerable? sorting) { - ArgumentNullException.ThrowIfNull(selectExpression); - - Select = new SearchSelect(selectExpression); + if (sorting is null) + return; + sortings ??= []; + sortings.AddRange(sorting); } /// /// Whether the query should be paginated. /// - public bool Paginate => ItemsPerPage > 0; + public bool Paginate => Page > 0; /// /// The number of the page that should be listed. @@ -119,10 +128,24 @@ public void SetSelectExpression(Expression> s /// /// /// The number of records that must be skipped. - public int GetSkipCount() => Paginate ? ItemsPerPage * (GetPageNumber() - 1) : 0; + public int GetSkipCount() => Paginate ? ItemsPerPage * (GetPageNumber() - 1) : Skip; + + /// + /// Determines the number of items to retrieve in a paginated query. + /// + /// + /// The number of items to retrieve. + ///
+ /// If pagination is enabled, returns the value of . + ///
+ /// If pagination is disabled and is greater than 0, returns the value of . + ///
+ /// Otherwise, returns 0. + ///
+ public int GetTakeCount() => Paginate ? ItemsPerPage : Take > 0 ? Take : 0; /// - /// Default values for each new created. + /// Default values for each new created. /// public static class Defaults { diff --git a/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs b/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs new file mode 100644 index 0000000..1388de2 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs @@ -0,0 +1,115 @@ + +using RoyalCode.SmartSearch.Mappings; +using RoyalCode.SmartSearch.Services; +using System.Linq.Expressions; + +namespace RoyalCode.SmartSearch.Defaults; + +/// +/// Default implementation of . +/// +/// The entity type. +public class Search : ISearch + where TEntity : class +{ + private readonly ICriteriaPerformer performer; + private readonly CriteriaOptions options; + + /// + /// Creates a new search. + /// + /// + /// + public Search(ICriteriaPerformer performer, CriteriaOptions options) + { + this.performer = performer; + this.options = options; + options.NoTracking(); + } + + /// + public ISearch Select() + where TDto : class + { + var select = new SearchSelect(); + return new Search(performer, options, select); + } + + /// + public ISearch Select(Expression> selectExpression) + where TDto : class + { + var select = new SearchSelect(selectExpression); + return new Search(performer, options, select); + } + + /// + public IResultList ToList() => performer.Prepare(options).ToResultList(); + + /// + public Task> ToListAsync(CancellationToken token) + => performer.Prepare(options).ToResultListAsync(token); + + /// + public Task> ToAsyncListAsync(CancellationToken token) + => performer.Prepare(options).ToAsyncListAsync(token); + + /// + public TEntity? FirstOrDefault() => performer.Prepare(options).FirstOrDefault(); + + /// + public Task FirstDefaultAsync(CancellationToken cancellationToken = default) + => performer.Prepare(options).FirstOrDefaultAsync(cancellationToken); + + /// + public TEntity Single() => performer.Prepare(options).Single(); + + /// + public Task SingleAsync(CancellationToken cancellationToken = default) + => performer.Prepare(options).SingleAsync(cancellationToken); +} + +public class Search : ISearch + where TEntity : class + where TDto : class +{ + private readonly ICriteriaPerformer performer; + private readonly CriteriaOptions options; + private readonly ISearchSelect searchSelect; + + public Search( + ICriteriaPerformer performer, + CriteriaOptions options, + ISearchSelect searchSelect) + { + this.performer = performer; + this.options = options; + this.searchSelect = searchSelect; + options.NoTracking(); + } + + /// + public IResultList ToList() => performer.Prepare(options).Select(searchSelect).ToResultList(); + + /// + public Task> ToListAsync(CancellationToken token) + => performer.Prepare(options).Select(searchSelect).ToResultListAsync(token); + + /// + public Task> ToAsyncListAsync(CancellationToken token) + => performer.Prepare(options).Select(searchSelect).ToAsyncListAsync(token); + + /// + public TDto? FirstOrDefault() => performer.Prepare(options).Select(searchSelect).FirstOrDefault(); + + /// + public Task FirstDefaultAsync(CancellationToken cancellationToken = default) + => performer.Prepare(options).Select(searchSelect).FirstOrDefaultAsync(cancellationToken); + + /// + public TDto Single() => performer.Prepare(options).Select(searchSelect).Single(); + + /// + public Task SingleAsync(CancellationToken cancellationToken = default) + => performer.Prepare(options).Select(searchSelect).SingleAsync(cancellationToken); +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/SearchFilter.cs b/src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs similarity index 57% rename from src/RoyalCode.SmartSearch.Core/SearchFilter.cs rename to src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs index 0aac4df..85b4879 100644 --- a/src/RoyalCode.SmartSearch.Core/SearchFilter.cs +++ b/src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs @@ -1,16 +1,16 @@ -namespace RoyalCode.SmartSearch.Core; +namespace RoyalCode.SmartSearch.Filtering; /// /// Information about a filter to be applied to a query. /// /// The filter instance. /// The filter type. -public class SearchFilter(TFilter Filter) : ISearchFilter +public sealed class EntityFilter(TFilter Filter) : IFilter where TFilter : class { /// - public void ApplyFilter(ISpecifierHandler handler) + public void ApplyFilter(ISpecifier specifier) { - handler.Handle(Filter); + specifier.Specify(Filter); } } diff --git a/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs b/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs new file mode 100644 index 0000000..47a425f --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs @@ -0,0 +1,13 @@ +namespace RoyalCode.SmartSearch.Filtering; + +/// +/// Information about a filter to be applied to a query. +/// +public interface IFilter +{ + /// + /// Applies the filter to the query by passing the filter to the handler. + /// + /// A specifier handler for applying filters to queries. + void ApplyFilter(ISpecifier specifier); +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/ISpecifierHandler.cs b/src/RoyalCode.SmartSearch.Core/Filtering/ISpecifier.cs similarity index 68% rename from src/RoyalCode.SmartSearch.Core/ISpecifierHandler.cs rename to src/RoyalCode.SmartSearch.Core/Filtering/ISpecifier.cs index 730087c..734abda 100644 --- a/src/RoyalCode.SmartSearch.Core/ISpecifierHandler.cs +++ b/src/RoyalCode.SmartSearch.Core/Filtering/ISpecifier.cs @@ -1,20 +1,20 @@ -namespace RoyalCode.SmartSearch.Core; +namespace RoyalCode.SmartSearch.Filtering; /// /// /// A handler for applying filters to queries. /// /// -/// This component is used by the , +/// This component is used by the , /// which stores the filter that will be used in the query specification. /// /// -public interface ISpecifierHandler +public interface ISpecifier { /// /// Receives the filter object that will be used to specify the query. /// /// The filter object. /// The filter type. - void Handle(TFilter filter) where TFilter : class; + void Specify(TFilter filter) where TFilter : class; } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/IFilterSpecifier.cs b/src/RoyalCode.SmartSearch.Core/IFilterSpecifier.cs deleted file mode 100644 index 0d357db..0000000 --- a/src/RoyalCode.SmartSearch.Core/IFilterSpecifier.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace RoyalCode.SmartSearch.Core; - -/// -/// Component that applies the filtering conditions to the query. -/// -/// The query type. -/// The filter type. -public interface IFilterSpecifier -{ - /// - /// Specify a query, apply the filter conditions to the query. - /// - /// The query object. - /// The filter object. - /// A new (or the same) query object. - TQuery Specify(TQuery query, TFilter filter); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/ISearchFilter.cs b/src/RoyalCode.SmartSearch.Core/ISearchFilter.cs deleted file mode 100644 index d95017d..0000000 --- a/src/RoyalCode.SmartSearch.Core/ISearchFilter.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace RoyalCode.SmartSearch.Core; - -/// -/// Information about a filter to be applied to a query. -/// -public interface ISearchFilter -{ - /// - /// Applies the filter to the query by passing the filter to the handler. - /// - /// A handler for applying filters to queries. - public abstract void ApplyFilter(ISpecifierHandler handler); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs new file mode 100644 index 0000000..ad6fc87 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs @@ -0,0 +1,25 @@ +namespace RoyalCode.SmartSearch.Mappings; + +/// +/// +/// Defines a select mapping between an entity and a DTO for search projections. +/// +/// +/// Used to apply a select expression or use the default mapping for the given types. +/// +/// +/// The entity type to select from. +/// The DTO type to project to. +public interface ISearchSelect + where TEntity : class + where TDto : class +{ + /// + /// Applies the select mapping to the specified . + /// + /// The search selector to apply the select mapping to. + /// + /// If a select expression was provided, it will be used; otherwise, the default mapping will be applied. + /// + public void ApplySelect(ISearchSelector searchSelector); +} diff --git a/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs new file mode 100644 index 0000000..6d75de7 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs @@ -0,0 +1,38 @@ +using System.Linq.Expressions; + +namespace RoyalCode.SmartSearch.Mappings; + +/// +/// +/// Defines a component capable of configuring a select projection for an entity type. +/// +/// +/// Used to specify how an entity should be projected to a DTO in search operations. +/// +/// +/// The entity type to select from. +public interface ISearchSelector + where TEntity : class +{ + /// + /// + /// Configures the selector to use the default mapping from to . + /// + /// + /// The mapping will be resolved by the search engine or selector factory. + /// + /// + /// The DTO type to project to. + void Select() + where TDto : class; + + /// + /// + /// Configures the selector to use a custom select expression from to . + /// + /// + /// The DTO type to project to. + /// The expression to select from the entity to the DTO. + void Select(Expression> selectExpression) + where TDto : class; +} diff --git a/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs b/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs new file mode 100644 index 0000000..a227443 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs @@ -0,0 +1,55 @@ +using System.Linq.Expressions; + +namespace RoyalCode.SmartSearch.Mappings; + +/// +/// +/// Represents a select mapping between an entity and a DTO for search projections. +/// +/// +/// Implements to provide a way to apply a select expression +/// or use the default mapping for the given types. +/// +/// +/// The entity type to select from. +/// The DTO type to project to. +public sealed class SearchSelect : ISearchSelect + where TEntity : class + where TDto : class +{ + /// + /// The select expression to project from to . + /// + private readonly Expression>? selectExpression; + + /// + /// Initializes a new instance of the class with a select expression. + /// + /// The expression to select from the entity to the DTO. + /// Thrown if is null. + public SearchSelect(Expression> selectExpression) + { + ArgumentNullException.ThrowIfNull(selectExpression); + this.selectExpression = selectExpression; + } + + /// + /// Initializes a new instance of the class using the default mapping. + /// + public SearchSelect() { } + + /// + /// Applies the select mapping to the specified . + /// + /// The search selector to apply the select mapping to. + /// + /// If a select expression was provided, it will be used; otherwise, the default mapping will be applied. + /// + public void ApplySelect(ISearchSelector searchSelector) + { + if (selectExpression is null) + searchSelector.Select(); + else + searchSelector.Select(selectExpression); + } +} diff --git a/src/RoyalCode.SmartSearch.Core/Pipeline/AllEntities.cs b/src/RoyalCode.SmartSearch.Core/Pipeline/AllEntities.cs deleted file mode 100644 index 29cd816..0000000 --- a/src/RoyalCode.SmartSearch.Core/Pipeline/AllEntities.cs +++ /dev/null @@ -1,173 +0,0 @@ -using RoyalCode.SmartSearch.Abstractions; -using System.Runtime.CompilerServices; - -namespace RoyalCode.SmartSearch.Core.Pipeline; - -/// -public class AllEntities : IAllEntities - where TEntity : class -{ - private readonly IPipelineFactory factory; - private readonly SearchCriteria criteria = new(); - - /// - /// Creates a new search with the to execute the query. - /// - /// The pipeline factory for create the all entities pipeline. - public AllEntities(IPipelineFactory factory) - { - this.factory = factory; - } - - /// - public IAllEntities FilterBy(TFilter filter) where TFilter : class - { - criteria.AddFilter(filter); - return this; - } - - /// - public IAllEntities OrderBy(ISorting sorting) - { - criteria.AddSorting(sorting); - return this; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ICollection Collect() - { - var pipeline = factory.CreateAllEntities(); - return pipeline.Execute(criteria); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task> CollectAsync(CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.ExecuteAsync(criteria, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Exists() - { - var pipeline = factory.CreateAllEntities(); - return pipeline.Any(criteria); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task ExistsAsync(CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.AnyAsync(criteria, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TEntity? First() - { - var pipeline = factory.CreateAllEntities(); - return pipeline.First(criteria); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task FirstAsync(CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.FirstAsync(criteria, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TEntity Single() - { - var pipeline = factory.CreateAllEntities(); - return pipeline.Single(criteria); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task SingleAsync(CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.SingleAsync(criteria, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void DeleteAll() - { - var pipeline = factory.CreateAllEntities(); - pipeline.RemoveAll(criteria); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task DeleteAllAsync(CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.RemoveAllAsync(criteria, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll(Action updateAction) - { - var pipeline = factory.CreateAllEntities(); - pipeline.UpdateAll(criteria, updateAction); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll(TData data, Action updateAction) - { - var pipeline = factory.CreateAllEntities(); - pipeline.UpdateAll(criteria, data, updateAction); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll( - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction) where TData : class - { - var pipeline = factory.CreateAllEntities(); - pipeline.UpdateAll(criteria, collection, entityIdGet, dataIdGet, updateAction); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task UpdateAllAsync(Action updateAction, CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.UpdateAllAsync(criteria, updateAction, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task UpdateAllAsync(TData data, Action updateAction, - CancellationToken cancellationToken = default) - { - var pipeline = factory.CreateAllEntities(); - return pipeline.UpdateAllAsync(criteria, data, updateAction, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task UpdateAllAsync( - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction, - CancellationToken cancellationToken = default) where TData : class - { - var pipeline = factory.CreateAllEntities(); - return pipeline.UpdateAllAsync(criteria, collection, entityIdGet, dataIdGet, updateAction, cancellationToken); - } -} diff --git a/src/RoyalCode.SmartSearch.Core/Pipeline/IAllEntitiesPipeline.cs b/src/RoyalCode.SmartSearch.Core/Pipeline/IAllEntitiesPipeline.cs deleted file mode 100644 index 1f7b5ce..0000000 --- a/src/RoyalCode.SmartSearch.Core/Pipeline/IAllEntitiesPipeline.cs +++ /dev/null @@ -1,173 +0,0 @@ - -namespace RoyalCode.SmartSearch.Core.Pipeline; - -/// -/// -/// A search pipeline for executing queries from the input criteria and get all entities. -/// -/// -/// This component will perform the various steps necessary to execute the query. -/// -/// -/// The entity type to query. -public interface IAllEntitiesPipeline - where TEntity : class -{ - /// - /// Execute the query and collect all entities. - /// - /// The criteria for the query. - /// A collection of the entities. - ICollection Execute(SearchCriteria searchCriteria); - - /// - /// Execute the query and collect all entities. - /// - /// The criteria for the query. - /// The cancellation token. - /// A collection of the entities. - Task> ExecuteAsync( - SearchCriteria searchCriteria, - CancellationToken cancellationToken = default); - - /// - /// Execute the query and verify if there are any entities. - /// - /// The criteria for the query. - /// True if there are any entities, otherwise false. - bool Any(SearchCriteria searchCriteria); - - /// - /// Execute the query and verify if there are any entities. - /// - /// The criteria for the query. - /// The cancellation token. - /// True if there are any entities, otherwise false. - Task AnyAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default); - - /// - /// Execute the query and get the first entity. - /// - /// The criteria for the query. - /// The first entity that meets the criteria. - TEntity? First(SearchCriteria searchCriteria); - - /// - /// Execute the query and get the first entity. - /// - /// The criteria for the query. - /// The cancellation token. - /// The first entity that meets the criteria. - Task FirstAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default); - - /// - /// Execute the query and get the first entity or throw an exception if there are no entities or more than one. - /// - /// The criteria for the query. - /// The first entity that meets the criteria, or an exception if there are no entities or more than one. - TEntity Single(SearchCriteria searchCriteria); - - /// - /// Execute the query and get the first entity or throw an exception if there are no entities or more than one. - /// - /// The criteria for the query. - /// The cancellation token. - /// The first entity that meets the criteria, or an exception if there are no entities or more than one. - Task SingleAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default); - - /// - /// Execute the query and remove all entities that meet the criteria. - /// - /// The criteria for the query. - void RemoveAll(SearchCriteria searchCriteria); - - /// - /// Execute the query and remove all entities that meet the criteria. - /// - /// The criteria for the query. - /// The cancellation token. - /// A task that represents the asynchronous operation. - Task RemoveAllAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default); - - /// - /// Execute the query and update all entities that meet the criteria. - /// - /// The criteria for the query. - /// The action to update the entities. - void UpdateAll(SearchCriteria searchCriteria, Action updateAction); - - /// - /// Execute the query and update all entities that meet the criteria. - /// - /// The criteria for the query. - /// The data used to update the entities. - /// The action to update the entities. - /// The type of the data used to update the entities. - void UpdateAll(SearchCriteria searchCriteria, TData data, Action updateAction); - - /// - /// Update all entities that meet the criteria. - /// - /// The criteria for the query. - /// A collection of data used to update the entities. - /// A function to get the entity id. - /// A function to get the data id. - /// The action to update the entities. - /// The type of the data used to update the entities. - /// The type of the id. - void UpdateAll( - SearchCriteria searchCriteria, - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction) - where TData : class; - - /// - /// Execute the query and update all entities that meet the criteria. - /// - /// The criteria for the query. - /// The action to update the entities. - /// The cancellation token. - /// A task that represents the asynchronous operation. - Task UpdateAllAsync( - SearchCriteria searchCriteria, - Action updateAction, - CancellationToken cancellationToken = default); - - /// - /// Execute the query and update all entities that meet the criteria. - /// - /// The criteria for the query. - /// The data used to update the entities. - /// The action to update the entities. - /// The cancellation token. - /// The type of the data used to update the entities. - /// A task that represents the asynchronous operation. - Task UpdateAllAsync( - SearchCriteria searchCriteria, - TData data, - Action updateAction, - CancellationToken cancellationToken = default); - - /// - /// Update all entities that meet the criteria. - /// - /// The criteria for the query. - /// The collection of data used to update the entities. - /// The function to get the entity id. - /// The function to get the data id. - /// The action to update the entities. - /// The cancellation token. - /// The type of the data used to update the entities. - /// The type of the id. - /// A task that represents the asynchronous operation. - Task UpdateAllAsync( - SearchCriteria searchCriteria, - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction, - CancellationToken cancellationToken = default) - where TData : class; -} diff --git a/src/RoyalCode.SmartSearch.Core/Pipeline/IPipelineFactory.cs b/src/RoyalCode.SmartSearch.Core/Pipeline/IPipelineFactory.cs deleted file mode 100644 index 48f7f35..0000000 --- a/src/RoyalCode.SmartSearch.Core/Pipeline/IPipelineFactory.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace RoyalCode.SmartSearch.Core.Pipeline; - -/// -/// -/// Factory to create search pipelines (), -/// and searches for all entities (). -/// -/// -public interface IPipelineFactory -{ - /// - /// Creates a to query entities. - /// - /// The entity type. - /// A new instance of a pipeline to execute the search. - ISearchPipeline Create() - where TEntity : class; - - /// - /// Creates a new to select DTOs from the query of entities. - /// - /// The entity type. - /// The DTO (to be selected) type. - /// A new instance of a pipeline to execute the search. - ISearchPipeline Create() - where TEntity : class - where TDto : class; - - /// - /// Creates a to query all entities. - /// - /// The entity type. - /// - /// A new instance of a pipeline to execute the search - /// - IAllEntitiesPipeline CreateAllEntities() - where TEntity : class; -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/Pipeline/ISearchPipeline.cs b/src/RoyalCode.SmartSearch.Core/Pipeline/ISearchPipeline.cs deleted file mode 100644 index 034fd60..0000000 --- a/src/RoyalCode.SmartSearch.Core/Pipeline/ISearchPipeline.cs +++ /dev/null @@ -1,39 +0,0 @@ -using RoyalCode.SmartSearch.Abstractions; - -namespace RoyalCode.SmartSearch.Core.Pipeline; - -/// -/// -/// A search pipeline for executing queries from the input criteria. -/// -/// -/// This component will perform the various steps necessary to execute the query. -/// -/// -/// The query model type. -public interface ISearchPipeline - where TModel : class -{ - /// - /// Execute the search and it returns a list of results. - /// - /// The criteria for the search. - /// A list of results. - IResultList Execute(SearchCriteria criteria); - - /// - /// Async execute the search and it returns a list of results. - /// - /// The criteria for the search. - /// The task cancellation token. - /// A task of a list of results. - Task> ExecuteAsync(SearchCriteria criteria, CancellationToken token); - - /// - /// Async execute the search and it returns a list of results. - /// - /// The criteria for the search. - /// The task cancellation token. - /// A task of an async list of results. - Task> AsyncExecuteAsync(SearchCriteria criteria, CancellationToken token); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/Pipeline/Search.cs b/src/RoyalCode.SmartSearch.Core/Pipeline/Search.cs deleted file mode 100644 index a464831..0000000 --- a/src/RoyalCode.SmartSearch.Core/Pipeline/Search.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Linq.Expressions; -using RoyalCode.SmartSearch.Abstractions; - -namespace RoyalCode.SmartSearch.Core.Pipeline; - -/// -public class Search : SearchBase - where TEntity : class -{ - private readonly IPipelineFactory factory; - - /// - /// Creates a new search with the to execute the query. - /// - /// A search pipeline factory. - public Search(IPipelineFactory factory) - { - this.factory = factory; - } - - /// - public override ISearch Select() - { - return new Search(factory, criteria); - } - - /// - public override ISearch Select(Expression> selectExpression) - { - criteria.SetSelectExpression(selectExpression); - return new Search(factory, criteria); - } - - /// - public override IResultList ToList() - { - var pipeline = factory.Create(); - return pipeline.Execute(criteria); - } - - /// - public override Task> ToListAsync(CancellationToken token) - { - var pipeline = factory.Create(); - return pipeline.ExecuteAsync(criteria, token); - } - - /// - public override Task> ToAsyncListAsync(CancellationToken token) - { - var pipeline = factory.Create(); - return pipeline.AsyncExecuteAsync(criteria, token); - } -} - -/// -public class Search : SearchBase - where TEntity : class - where TDto : class -{ - private readonly IPipelineFactory factory; - - /// - /// Creates a new search. - /// - /// The pipeline factory for create the search pipeline. - /// The previous criteria. - public Search(IPipelineFactory factory, SearchCriteria criteria) : base(criteria) - { - this.factory = factory; - } - - /// - public override IResultList ToList() - { - var pipeline = factory.Create(); - return pipeline.Execute(criteria); - } - - /// - public override Task> ToListAsync(CancellationToken token) - { - var pipeline = factory.Create(); - return pipeline.ExecuteAsync(criteria, token); - } - - /// - public override Task> ToAsyncListAsync(CancellationToken token) - { - var pipeline = factory.Create(); - return pipeline.AsyncExecuteAsync(criteria, token); - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj b/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj index e8b785a..7aa73bc 100644 --- a/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj +++ b/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj @@ -4,6 +4,7 @@ $(AspTargets) + RoyalCode.SmartSearch diff --git a/src/RoyalCode.SmartSearch.Core/SearchBase.cs b/src/RoyalCode.SmartSearch.Core/SearchBase.cs deleted file mode 100644 index 015da5e..0000000 --- a/src/RoyalCode.SmartSearch.Core/SearchBase.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.Linq.Expressions; -using RoyalCode.SmartSearch.Abstractions; - -namespace RoyalCode.SmartSearch.Core; - -/// -public abstract class SearchBase : ISearch - where TEntity : class -{ - /// - /// The criteria for performing the search. - /// - protected readonly SearchCriteria criteria = new(); - - /// - public ISearch UsePages(int itemsPerPage = 10) - { - criteria.ItemsPerPage = itemsPerPage; - return this; - } - - /// - public ISearch FetchPage(int pageNumber) - { - criteria.Page = pageNumber; - return this; - } - - /// - public ISearch UseLastCount(int lastCount) - { - criteria.LastCount = lastCount; - return this; - } - - /// - public ISearch UseCount(bool useCount = true) - { - criteria.UseCount = useCount; - return this; - } - - /// - public ISearch FilterBy(TFilter filter) - where TFilter : class - { - criteria.AddFilter(filter); - return this; - } - - /// - public ISearch OrderBy(ISorting sorting) - { - criteria.AddSorting(sorting); - return this; - } - - /// - public abstract ISearch Select() - where TDto : class; - - /// - public abstract ISearch Select(Expression> selectExpression) - where TDto : class; - - /// - public abstract IResultList ToList(); - - /// - public abstract Task> ToListAsync(CancellationToken token); - - /// - public abstract Task> ToAsyncListAsync(CancellationToken token); -} - -/// -public abstract class SearchBase : ISearch - where TEntity : class - where TDto : class -{ - /// - /// The criteria for performing the search. - /// - protected readonly SearchCriteria criteria; - - /// - /// Initialize the base criteria with the . - /// - /// - protected SearchBase(SearchCriteria criteria) - { - this.criteria = criteria; - } - - /// - public ISearch UsePages(int itemsPerPage = 10) - { - criteria.ItemsPerPage = itemsPerPage; - return this; - } - - /// - public ISearch FetchPage(int pageNumber) - { - criteria.Page = pageNumber; - return this; - } - - /// - public ISearch UseLastCount(int lastCount) - { - criteria.LastCount = lastCount; - return this; - } - - /// - public ISearch UseCount(bool useCount = true) - { - criteria.UseCount = useCount; - return this; - } - - /// - public ISearch FilterBy(TFilter filter) - where TFilter : class - { - criteria.AddFilter(filter); - return this; - } - - /// - public abstract IResultList ToList(); - - /// - public abstract Task> ToListAsync(CancellationToken token); - - /// - public abstract Task> ToAsyncListAsync(CancellationToken token); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/SearchSelect.cs b/src/RoyalCode.SmartSearch.Core/SearchSelect.cs deleted file mode 100644 index 1d2f492..0000000 --- a/src/RoyalCode.SmartSearch.Core/SearchSelect.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq.Expressions; - -namespace RoyalCode.SmartSearch.Core; - -/// -/// Information about the select to be applied to the query. -/// -public sealed class SearchSelect -{ - /// - /// Creates a new instance of . - /// - /// - public SearchSelect(Expression selectExpression) - { - SelectExpression = selectExpression; - } - - /// - /// The expression to be used in the select operation. - /// - public Expression SelectExpression { get; } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/Services/ICriteriaPerformer.cs b/src/RoyalCode.SmartSearch.Core/Services/ICriteriaPerformer.cs new file mode 100644 index 0000000..0b431c5 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Services/ICriteriaPerformer.cs @@ -0,0 +1,18 @@ +using RoyalCode.SmartSearch.Defaults; + +namespace RoyalCode.SmartSearch.Services; + +/// +/// A service capable of accessing the database and running a query using the Criteria options. +/// +/// The entity type to query. +public interface ICriteriaPerformer + where TEntity : class +{ + /// + /// Prepares the query with the criteria options for execution. + /// + /// The criteria options for performing the search. + /// A prepared query to perform. + IPreparedQuery Prepare(CriteriaOptions options); +} diff --git a/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs b/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs new file mode 100644 index 0000000..502f7d7 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs @@ -0,0 +1,32 @@ +using RoyalCode.SmartSearch.Mappings; + +namespace RoyalCode.SmartSearch.Services; + +public interface IPreparedQuery + where T : class +{ + bool Exists(); + + Task ExistsAsync(CancellationToken ct = default); + + T? FirstOrDefault(); + + Task FirstOrDefaultAsync(CancellationToken ct = default); + + T Single(); + + Task SingleAsync(CancellationToken ct = default); + + IReadOnlyList ToList(); + + Task> ToListAsync(CancellationToken ct); + + IResultList ToResultList(); + + Task> ToResultListAsync(CancellationToken ct); + + Task> ToAsyncListAsync(CancellationToken ct); + + IPreparedQuery Select(ISearchSelect select) + where TDto : class; +} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs b/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs index 9795a4e..cc7e26d 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Core; using RoyalCode.SmartSearch.Core.Pipeline; +using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.Linq; using RoyalCode.SmartSearch.Linq.Filter; using System.Runtime.CompilerServices; @@ -24,28 +24,28 @@ public AllEntitiesPipeline( /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Any(SearchCriteria searchCriteria) + public bool Any(CriteriaOptions searchCriteria) { return PrepareQuery(searchCriteria).Any(); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task AnyAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default) + public Task AnyAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) { return PrepareQuery(searchCriteria).AnyAsync(cancellationToken); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ICollection Execute(SearchCriteria searchCriteria) + public ICollection Execute(CriteriaOptions searchCriteria) { return PrepareQuery(searchCriteria).ToList(); } /// public async Task> ExecuteAsync( - SearchCriteria searchCriteria, + CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) { return await PrepareQuery(searchCriteria).ToListAsync(cancellationToken); @@ -53,21 +53,21 @@ public async Task> ExecuteAsync( /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TEntity? First(SearchCriteria searchCriteria) + public TEntity? First(CriteriaOptions searchCriteria) { return PrepareQuery(searchCriteria).FirstOrDefault(); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task FirstAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default) + public Task FirstAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) { return PrepareQuery(searchCriteria).FirstOrDefaultAsync(cancellationToken); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveAll(SearchCriteria searchCriteria) + public void RemoveAll(CriteriaOptions searchCriteria) { var query = PrepareQuery(searchCriteria); var removable = queryableProvider.GetRemovable(); @@ -76,7 +76,7 @@ public void RemoveAll(SearchCriteria searchCriteria) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task RemoveAllAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default) + public Task RemoveAllAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) { var query = PrepareQuery(searchCriteria); var removable = queryableProvider.GetRemovable(); @@ -85,21 +85,21 @@ public Task RemoveAllAsync(SearchCriteria searchCriteria, CancellationToken canc /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TEntity Single(SearchCriteria searchCriteria) + public TEntity Single(CriteriaOptions searchCriteria) { return PrepareQuery(searchCriteria).Single(); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task SingleAsync(SearchCriteria searchCriteria, CancellationToken cancellationToken = default) + public Task SingleAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) { return PrepareQuery(searchCriteria).SingleAsync(cancellationToken); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll(SearchCriteria searchCriteria, Action updateAction) + public void UpdateAll(CriteriaOptions searchCriteria, Action updateAction) { var query = PrepareQuery(searchCriteria); foreach(var entity in query) @@ -110,7 +110,7 @@ public void UpdateAll(SearchCriteria searchCriteria, Action updateActio /// public async Task UpdateAllAsync( - SearchCriteria searchCriteria, + CriteriaOptions searchCriteria, Action updateAction, CancellationToken cancellationToken = default) { @@ -123,7 +123,7 @@ public async Task UpdateAllAsync( /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll(SearchCriteria searchCriteria, TData data, Action updateAction) + public void UpdateAll(CriteriaOptions searchCriteria, TData data, Action updateAction) { var query = PrepareQuery(searchCriteria); foreach(var entity in query) @@ -135,7 +135,7 @@ public void UpdateAll(SearchCriteria searchCriteria, TData data, Action [MethodImpl(MethodImplOptions.AggressiveInlining)] public async Task UpdateAllAsync( - SearchCriteria searchCriteria, + CriteriaOptions searchCriteria, TData data, Action updateAction, CancellationToken cancellationToken = default) @@ -149,7 +149,7 @@ public async Task UpdateAllAsync( /// public void UpdateAll( - SearchCriteria searchCriteria, + CriteriaOptions searchCriteria, ICollection collection, Func entityIdGet, Func dataIdGet, @@ -172,7 +172,7 @@ public void UpdateAll( /// public async Task UpdateAllAsync( - SearchCriteria searchCriteria, + CriteriaOptions searchCriteria, ICollection collection, Func entityIdGet, Func dataIdGet, diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs index 16d8e94..ba4b71d 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs @@ -2,8 +2,8 @@ using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore; using RoyalCode.SmartSearch.Abstractions; -using RoyalCode.SmartSearch.Core; using RoyalCode.SmartSearch.Core.Pipeline; +using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.Linq; using RoyalCode.SmartSearch.Linq.Filter; @@ -32,7 +32,7 @@ public SearchPipeline( { } /// - public IResultList Execute(SearchCriteria criteria) + public IResultList Execute(CriteriaOptions criteria) { var sortedQuery = PrepareQuery(criteria); @@ -71,7 +71,7 @@ public IResultList Execute(SearchCriteria criteria) } /// - public async Task> ExecuteAsync(SearchCriteria criteria, CancellationToken token) + public async Task> ExecuteAsync(CriteriaOptions criteria, CancellationToken token) { var sortedQuery = PrepareQuery(criteria); @@ -110,7 +110,7 @@ public async Task> ExecuteAsync(SearchCriteria criteria, Ca } /// - public async Task> AsyncExecuteAsync(SearchCriteria criteria, CancellationToken token) + public async Task> AsyncExecuteAsync(CriteriaOptions criteria, CancellationToken token) { var sortedQuery = PrepareQuery(criteria); @@ -173,7 +173,7 @@ public SearchPipeline( } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private IQueryable Select(IQueryable query, SearchCriteria criteria) + private IQueryable Select(IQueryable query, CriteriaOptions criteria) { var selectExpression = (Expression>?)criteria.Select?.SelectExpression ?? selector?.GetSelectExpression() @@ -183,7 +183,7 @@ private IQueryable Select(IQueryable query, SearchCriteria criter } /// - public IResultList Execute(SearchCriteria criteria) + public IResultList Execute(CriteriaOptions criteria) { var sortedQuery = PrepareQuery(criteria); var selectQuery = Select(sortedQuery, criteria); @@ -220,7 +220,7 @@ public IResultList Execute(SearchCriteria criteria) } /// - public async Task> ExecuteAsync(SearchCriteria criteria, CancellationToken token) + public async Task> ExecuteAsync(CriteriaOptions criteria, CancellationToken token) { var sortedQuery = PrepareQuery(criteria); var selectQuery = Select(sortedQuery, criteria); @@ -257,7 +257,7 @@ public async Task> ExecuteAsync(SearchCriteria criteria, Cance } /// - public async Task> AsyncExecuteAsync(SearchCriteria criteria, CancellationToken token) + public async Task> AsyncExecuteAsync(CriteriaOptions criteria, CancellationToken token) { var sortedQuery = PrepareQuery(criteria); var selectQuery = Select(sortedQuery, criteria); diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs index 3a6733d..a8d993e 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -using RoyalCode.SmartSearch.Core; using RoyalCode.SmartSearch.Core.Pipeline; +using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.Linq; using RoyalCode.SmartSearch.Linq.Filter; @@ -58,7 +58,7 @@ protected SearchPipelineBase( /// Search criteria. /// A prepared query. [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected IQueryable PrepareQuery(SearchCriteria criteria) + protected IQueryable PrepareQuery(CriteriaOptions criteria) { var baseQuery = queryableProvider.GetQueryable(); diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs b/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs index 829f34d..4574366 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs @@ -1,17 +1,17 @@ -using RoyalCode.SmartSearch.Core; +using RoyalCode.SmartSearch.Filtering; namespace RoyalCode.SmartSearch.Linq.Filter; /// /// -/// Default implementation of +/// Default implementation of /// that applies the filters to a , /// retrieving the filters specifiers from the , /// which in turn knows how to apply the filter specification to the query. /// /// /// The query model type. -public sealed class SpecifierHandler : ISpecifierHandler +public sealed class SpecifierHandler : ISpecifier where TModel : class { private readonly ISpecifierFactory factory; @@ -33,7 +33,7 @@ public SpecifierHandler(ISpecifierFactory factory, IQueryable query) public IQueryable Query { get; private set; } /// - public void Handle(TFilter filter) where TFilter : class + public void Specify(TFilter filter) where TFilter : class { var specifier = factory.GetSpecifier(); if (specifier is not null) diff --git a/src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs b/src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs index f07e759..78697a0 100644 --- a/src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs +++ b/src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs @@ -12,10 +12,4 @@ public interface IQueryableProvider ///
/// An instance. IQueryable GetQueryable(); - - /// - /// Get a new queryable for the entity embedded in a removable component. - /// - /// - IRemovable GetRemovable(); } diff --git a/src/RoyalCode.SmartSearch.Linq/IRemovable.cs b/src/RoyalCode.SmartSearch.Linq/IRemovable.cs index c8d9382..8c593fc 100644 --- a/src/RoyalCode.SmartSearch.Linq/IRemovable.cs +++ b/src/RoyalCode.SmartSearch.Linq/IRemovable.cs @@ -4,6 +4,7 @@ /// Componente to provide a queryable and methods to remove entities from the database. ///
/// The entity type. +[Obsolete] public interface IRemovable where TEntity : class { diff --git a/src/RoyalCode.SmartSearch.Linq/ISorter.cs b/src/RoyalCode.SmartSearch.Linq/ISorter.cs index 40d26fb..03b8f59 100644 --- a/src/RoyalCode.SmartSearch.Linq/ISorter.cs +++ b/src/RoyalCode.SmartSearch.Linq/ISorter.cs @@ -1,4 +1,3 @@ -using RoyalCode.SmartSearch.Abstractions; using RoyalCode.SmartSearch.Linq.Sorter; namespace RoyalCode.SmartSearch.Linq; diff --git a/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs b/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs index 4f118b8..0b71ab6 100644 --- a/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs +++ b/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs @@ -1,4 +1,5 @@ using RoyalCode.SmartSearch.Core; +using RoyalCode.SmartSearch.Filtering; namespace RoyalCode.SmartSearch.Linq; @@ -8,7 +9,7 @@ namespace RoyalCode.SmartSearch.Linq; ///
/// The model of the . /// The filter type. -public interface ISpecifier : IFilterSpecifier, TFilter> +public interface ISpecifier : ISpecifier where TModel : class where TFilter : class { } \ No newline at end of file From 8c0a07f942614f45d507a2bae398087bfe3325b0 Mon Sep 17 00:00:00 2001 From: Glauco Knihs Date: Tue, 24 Jun 2025 22:43:41 -0300 Subject: [PATCH 3/9] refatoring --- .../Defaults/Criteria.cs | 1 + .../Defaults/CriteriaOptions.cs | 10 +- .../Filtering/EntityFilter.cs | 2 +- .../Filtering/IFilter.cs | 2 +- .../{ISpecifier.cs => IFilterHandler.cs} | 2 +- .../Mappings/ISearchSelect.cs | 21 +- .../Mappings/ISearchSelector.cs | 38 ---- .../Mappings/SearchSelect.cs | 16 +- .../Services/IPreparedQuery.cs | 69 +++++++ .../AllEntitiesPipeline.cs | 2 +- ...workSearchesServiceCollectionExtensions.cs | 5 +- .../PipelineFactory.cs | 5 +- .../QueryableProvider.cs | 2 +- .../SearchPipeline.cs | 8 +- .../SearchPipelineBase.cs | 4 +- .../CriterionResolution.cs | 3 +- .../DefaultSpecifierFunctionGenerator.cs | 3 +- .../DefaultSpecifierGenerator.cs | 3 +- .../FilterHandler.cs} | 9 +- .../Filtering/ISpecifier.cs | 20 ++ .../ISpecifierFunctionGenerator.cs | 3 +- .../ISpecifierGenerator.cs | 3 +- .../ISpecifierGeneratorOptions.cs | 2 +- .../InternalSpecifier.cs | 2 +- .../InternalSpecifierGeneratorOptions.cs | 2 +- .../{Filter => Filtering}/SpecifierFactory.cs | 7 +- .../SpecifierGeneratorOptions.cs | 2 +- .../SpecifierGeneratorPropertyOptions.cs | 2 +- .../{Filter => Filtering}/SpecifiersMap.cs | 2 +- src/RoyalCode.SmartSearch.Linq/IRemovable.cs | 24 --- .../ISearchConfigurations.cs | 6 +- src/RoyalCode.SmartSearch.Linq/ISorter.cs | 40 ---- src/RoyalCode.SmartSearch.Linq/ISpecifier.cs | 15 -- .../EnumSelectorPropertyConverter.cs | 2 +- .../EnumerableSelectorPropertyResolver.cs | 2 +- .../Converters/ISelectResolver.cs | 2 +- .../Converters/ISelectorPropertyConverter.cs | 3 +- .../Converters/ISelectorPropertyResolver.cs | 2 +- .../NullableSelectorPropertyConverter.cs | 2 +- .../Converters/SelectResolution.cs | 2 +- .../SubSelectSelectorPropertyResolver.cs | 2 +- .../DefaultSelectorExpressionGenerator.cs | 4 +- .../DefaultSelectorGenerator.cs | 2 +- .../{ => Mappings}/ISelector.cs | 2 +- .../ISelectorExpressionGenerator.cs | 2 +- .../ISelectorGenerator.cs | 3 +- .../InternalSelector.cs | 2 +- .../{Selector => Mappings}/SelectorFactory.cs | 5 +- .../SelectorNotFoundException.cs | 3 +- .../{Selector => Mappings}/SelectorsMap.cs | 2 +- .../SearchesServiceCollectionExtensions.cs | 7 +- .../Services/CriteriaPerformer.cs | 64 ++++++ .../Services/CriteriaQuery.cs | 183 ++++++++++++++++++ .../{ => Services}/IQueryableProvider.cs | 9 +- .../ISelectorFactory.cs | 5 +- .../{Filter => Services}/ISpecifierFactory.cs | 11 +- .../Sorter/DefaultSorter.cs | 71 ------- .../Sorter/IOrderByBuilder.cs | 22 --- .../DefaultOrderByGenerator.cs | 2 +- .../{Sorter => Sortings}/IOrderByGenerator.cs | 2 +- .../{Sorter => Sortings}/IOrderByHandler.cs | 4 +- .../{Sorter => Sortings}/IOrderByProvider.cs | 2 +- .../InvalidOrderByExpressionException.cs | 3 +- .../{Sorter => Sortings}/OrderByBuilder.cs | 19 +- .../{Sorter => Sortings}/OrderByHandler.cs | 6 +- .../OrderByHandlersMap.cs | 2 +- .../OrderByNotSupportedException.cs | 3 +- .../{Sorter => Sortings}/OrderByProvider.cs | 2 +- .../SpecifierFunctionGeneratorTests.cs | 2 +- 69 files changed, 459 insertions(+), 337 deletions(-) rename src/RoyalCode.SmartSearch.Core/Filtering/{ISpecifier.cs => IFilterHandler.cs} (95%) delete mode 100644 src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/CriterionResolution.cs (95%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/DefaultSpecifierFunctionGenerator.cs (99%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/DefaultSpecifierGenerator.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Filter/SpecifierHandler.cs => Filtering/FilterHandler.cs} (80%) create mode 100644 src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifier.cs rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/ISpecifierFunctionGenerator.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/ISpecifierGenerator.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/ISpecifierGeneratorOptions.cs (96%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/InternalSpecifier.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/InternalSpecifierGeneratorOptions.cs (97%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/SpecifierFactory.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/SpecifierGeneratorOptions.cs (96%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/SpecifierGeneratorPropertyOptions.cs (96%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Filtering}/SpecifiersMap.cs (97%) delete mode 100644 src/RoyalCode.SmartSearch.Linq/IRemovable.cs delete mode 100644 src/RoyalCode.SmartSearch.Linq/ISorter.cs delete mode 100644 src/RoyalCode.SmartSearch.Linq/ISpecifier.cs rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/EnumSelectorPropertyConverter.cs (97%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/EnumerableSelectorPropertyResolver.cs (98%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/ISelectResolver.cs (95%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/ISelectorPropertyConverter.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/ISelectorPropertyResolver.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/NullableSelectorPropertyConverter.cs (96%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/SelectResolution.cs (88%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/Converters/SubSelectSelectorPropertyResolver.cs (97%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/DefaultSelectorExpressionGenerator.cs (97%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/DefaultSelectorGenerator.cs (95%) rename src/RoyalCode.SmartSearch.Linq/{ => Mappings}/ISelector.cs (95%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/ISelectorExpressionGenerator.cs (94%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/ISelectorGenerator.cs (91%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/InternalSelector.cs (91%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/SelectorFactory.cs (95%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/SelectorNotFoundException.cs (88%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Mappings}/SelectorsMap.cs (97%) create mode 100644 src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs create mode 100644 src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs rename src/RoyalCode.SmartSearch.Linq/{ => Services}/IQueryableProvider.cs (53%) rename src/RoyalCode.SmartSearch.Linq/{Selector => Services}/ISelectorFactory.cs (85%) rename src/RoyalCode.SmartSearch.Linq/{Filter => Services}/ISpecifierFactory.cs (57%) delete mode 100644 src/RoyalCode.SmartSearch.Linq/Sorter/DefaultSorter.cs delete mode 100644 src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByBuilder.cs rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/DefaultOrderByGenerator.cs (94%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/IOrderByGenerator.cs (93%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/IOrderByHandler.cs (88%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/IOrderByProvider.cs (97%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/InvalidOrderByExpressionException.cs (92%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/OrderByBuilder.cs (68%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/OrderByHandler.cs (90%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/OrderByHandlersMap.cs (97%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/OrderByNotSupportedException.cs (92%) rename src/RoyalCode.SmartSearch.Linq/{Sorter => Sortings}/OrderByProvider.cs (95%) diff --git a/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs b/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs index 70d47bb..9834eef 100644 --- a/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs +++ b/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs @@ -70,6 +70,7 @@ public ICriteria SkipTake(int skip, int take) public ICriteria UseLastCount(int lastCount) { options.LastCount = lastCount; + options.UseCount = true; return this; } diff --git a/src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs b/src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs index 1ff2d34..46cd6ec 100644 --- a/src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs +++ b/src/RoyalCode.SmartSearch.Core/Defaults/CriteriaOptions.cs @@ -9,7 +9,11 @@ public sealed class CriteriaOptions { private List? filters; private List? sortings; - private bool trackingEnabled = true; + + /// + /// Gets or sets a value indicating whether tracking is enabled. + /// + public bool TrackingEnabled { get; private set; } = true; /// /// Get all filters. @@ -17,7 +21,7 @@ public sealed class CriteriaOptions public IReadOnlyList Filters => filters ?? []; /// - /// Get all sortings. + /// Get all sorting. /// public IReadOnlyList Sortings => sortings ?? []; @@ -69,7 +73,7 @@ public sealed class CriteriaOptions /// public void NoTracking() { - trackingEnabled = false; + TrackingEnabled = false; } /// diff --git a/src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs b/src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs index 85b4879..9889aa5 100644 --- a/src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs +++ b/src/RoyalCode.SmartSearch.Core/Filtering/EntityFilter.cs @@ -9,7 +9,7 @@ public sealed class EntityFilter(TFilter Filter) : IFilter where TFilter : class { /// - public void ApplyFilter(ISpecifier specifier) + public void ApplyFilter(IFilterHandler specifier) { specifier.Specify(Filter); } diff --git a/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs b/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs index 47a425f..70d4205 100644 --- a/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs +++ b/src/RoyalCode.SmartSearch.Core/Filtering/IFilter.cs @@ -9,5 +9,5 @@ public interface IFilter /// Applies the filter to the query by passing the filter to the handler. /// /// A specifier handler for applying filters to queries. - void ApplyFilter(ISpecifier specifier); + void ApplyFilter(IFilterHandler specifier); } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Core/Filtering/ISpecifier.cs b/src/RoyalCode.SmartSearch.Core/Filtering/IFilterHandler.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Core/Filtering/ISpecifier.cs rename to src/RoyalCode.SmartSearch.Core/Filtering/IFilterHandler.cs index 734abda..ee899be 100644 --- a/src/RoyalCode.SmartSearch.Core/Filtering/ISpecifier.cs +++ b/src/RoyalCode.SmartSearch.Core/Filtering/IFilterHandler.cs @@ -9,7 +9,7 @@ namespace RoyalCode.SmartSearch.Filtering; /// which stores the filter that will be used in the query specification. /// ///
-public interface ISpecifier +public interface IFilterHandler { /// /// Receives the filter object that will be used to specify the query. diff --git a/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs index ad6fc87..e8f9ad4 100644 --- a/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs +++ b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelect.cs @@ -1,4 +1,6 @@ -namespace RoyalCode.SmartSearch.Mappings; +using System.Linq.Expressions; + +namespace RoyalCode.SmartSearch.Mappings; /// /// @@ -9,17 +11,20 @@ /// /// /// The entity type to select from. -/// The DTO type to project to. +/// The data transfer object (DTO) type to project to. public interface ISearchSelect where TEntity : class where TDto : class { /// - /// Applies the select mapping to the specified . + /// + /// Gets the expression used to project an entity of type into a data transfer + /// object (DTO) of type . + /// + /// + /// This expression is optional and can be used to customize the selection of properties from the entity + /// to the DTO. When not provided, the default mapping will be used if available. + /// /// - /// The search selector to apply the select mapping to. - /// - /// If a select expression was provided, it will be used; otherwise, the default mapping will be applied. - /// - public void ApplySelect(ISearchSelector searchSelector); + Expression>? SelectExpression { get; } } diff --git a/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs b/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs deleted file mode 100644 index 6d75de7..0000000 --- a/src/RoyalCode.SmartSearch.Core/Mappings/ISearchSelector.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Linq.Expressions; - -namespace RoyalCode.SmartSearch.Mappings; - -/// -/// -/// Defines a component capable of configuring a select projection for an entity type. -/// -/// -/// Used to specify how an entity should be projected to a DTO in search operations. -/// -/// -/// The entity type to select from. -public interface ISearchSelector - where TEntity : class -{ - /// - /// - /// Configures the selector to use the default mapping from to . - /// - /// - /// The mapping will be resolved by the search engine or selector factory. - /// - /// - /// The DTO type to project to. - void Select() - where TDto : class; - - /// - /// - /// Configures the selector to use a custom select expression from to . - /// - /// - /// The DTO type to project to. - /// The expression to select from the entity to the DTO. - void Select(Expression> selectExpression) - where TDto : class; -} diff --git a/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs b/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs index a227443..eae6fe8 100644 --- a/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs +++ b/src/RoyalCode.SmartSearch.Core/Mappings/SearchSelect.cs @@ -38,18 +38,6 @@ public SearchSelect(Expression> selectExpression) /// public SearchSelect() { } - /// - /// Applies the select mapping to the specified . - /// - /// The search selector to apply the select mapping to. - /// - /// If a select expression was provided, it will be used; otherwise, the default mapping will be applied. - /// - public void ApplySelect(ISearchSelector searchSelector) - { - if (selectExpression is null) - searchSelector.Select(); - else - searchSelector.Select(selectExpression); - } + /// + public Expression>? SelectExpression => selectExpression; } diff --git a/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs b/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs index 502f7d7..fbd0ad1 100644 --- a/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs +++ b/src/RoyalCode.SmartSearch.Core/Services/IPreparedQuery.cs @@ -2,31 +2,100 @@ namespace RoyalCode.SmartSearch.Services; +/// +/// +/// Represents a prepared query for a specific entity type, allowing execution of search operations +/// such as existence checks, retrieval of single or multiple results, and projection to DTOs. +/// +/// +/// This interface provides both synchronous and asynchronous methods for querying, as well as support +/// for result list abstractions and custom select projections. +/// +/// +/// The entity type for which the query is prepared. public interface IPreparedQuery where T : class { + /// + /// Determines whether any entities exist that match the prepared query. + /// + /// True if any entities exist; otherwise, false. bool Exists(); + /// + /// Asynchronously determines whether any entities exist that match the prepared query. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains true if any entities exist; otherwise, false. Task ExistsAsync(CancellationToken ct = default); + /// + /// Returns the first entity that matches the prepared query, or null if no entity is found. + /// + /// The first entity found, or null if no entity matches the query. T? FirstOrDefault(); + /// + /// Asynchronously returns the first entity that matches the prepared query, or null if no entity is found. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the first entity found, or null if no entity matches the query. Task FirstOrDefaultAsync(CancellationToken ct = default); + /// + /// Returns the single entity that matches the prepared query. + /// + /// The single entity found. + /// Thrown if no entity or more than one entity matches the query. T Single(); + /// + /// Asynchronously returns the single entity that matches the prepared query. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains the single entity found. + /// Thrown if no entity or more than one entity matches the query. Task SingleAsync(CancellationToken ct = default); + /// + /// Returns a read-only list of all entities that match the prepared query. + /// + /// A read-only list of entities. IReadOnlyList ToList(); + /// + /// Asynchronously returns a read-only list of all entities that match the prepared query. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains a read-only list of entities. Task> ToListAsync(CancellationToken ct); + /// + /// Returns a result list abstraction containing all entities that match the prepared query. + /// + /// An containing the entities and additional result metadata. IResultList ToResultList(); + /// + /// Asynchronously returns a result list abstraction containing all entities that match the prepared query. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains an . Task> ToResultListAsync(CancellationToken ct); + /// + /// Asynchronously returns an asynchronous result list abstraction containing all entities that match the prepared query. + /// + /// A cancellation token to observe while waiting for the task to complete. + /// A task that represents the asynchronous operation. The task result contains an . Task> ToAsyncListAsync(CancellationToken ct); + /// + /// Projects the prepared query to a different DTO type using the specified select mapping. + /// + /// The DTO type to project to. + /// The select mapping to apply for the projection. + /// A new representing the projected query. IPreparedQuery Select(ISearchSelect select) where TDto : class; } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs b/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs index cc7e26d..1a5b661 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs @@ -2,7 +2,7 @@ using RoyalCode.SmartSearch.Core.Pipeline; using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Filter; +using RoyalCode.SmartSearch.Linq.Services; using System.Runtime.CompilerServices; namespace RoyalCode.SmartSearch.EntityFramework; diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs index 701605b..720d6f3 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs @@ -6,9 +6,8 @@ using RoyalCode.SmartSearch.EntityFramework.Configurations; using RoyalCode.SmartSearch.EntityFramework.Internals; using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Filter; -using RoyalCode.SmartSearch.Linq.Selector; -using RoyalCode.SmartSearch.Linq.Sorter; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; namespace Microsoft.Extensions.DependencyInjection; diff --git a/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs b/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs index fb5e5b9..445b77e 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs @@ -2,9 +2,8 @@ using RoyalCode.OperationHint.Abstractions; using RoyalCode.SmartSearch.Core.Pipeline; using RoyalCode.SmartSearch.EntityFramework.Internals; -using RoyalCode.SmartSearch.Linq.Filter; -using RoyalCode.SmartSearch.Linq.Selector; -using RoyalCode.SmartSearch.Linq.Sorter; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; namespace RoyalCode.SmartSearch.EntityFramework; diff --git a/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs b/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs index 3e6f5c6..7f00bb7 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using RoyalCode.OperationHint.Abstractions; -using RoyalCode.SmartSearch.Linq; +using RoyalCode.SmartSearch.Linq.Services; namespace RoyalCode.SmartSearch.EntityFramework; diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs index ba4b71d..e81a49f 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs @@ -1,15 +1,13 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Abstractions; -using RoyalCode.SmartSearch.Core.Pipeline; using RoyalCode.SmartSearch.Defaults; -using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Filter; +using RoyalCode.SmartSearch.Linq.Mappings; +using RoyalCode.SmartSearch.Linq.Services; namespace RoyalCode.SmartSearch.EntityFramework; -#pragma warning disable S3358 // ifs ternarios should not be nested +#pragma warning disable S3358 // ifs ternaries should not be nested /// /// diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs index a8d993e..e01baab 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs @@ -3,6 +3,8 @@ using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.Linq; using RoyalCode.SmartSearch.Linq.Filter; +using RoyalCode.SmartSearch.Linq.Filtering; +using RoyalCode.SmartSearch.Linq.Services; namespace RoyalCode.SmartSearch.EntityFramework; @@ -65,7 +67,7 @@ protected IQueryable PrepareQuery(CriteriaOptions criteria) var queryFilters = criteria.Filters; if (queryFilters.Count is not 0) { - var handler = new SpecifierHandler(specifierFactory, baseQuery); + var handler = new FilterHandler(specifierFactory, baseQuery); foreach (var searchFilter in queryFilters) { searchFilter.ApplyFilter(handler); diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/CriterionResolution.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/CriterionResolution.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Linq/Filter/CriterionResolution.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/CriterionResolution.cs index b43f0ee..d53c049 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/CriterionResolution.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/CriterionResolution.cs @@ -1,9 +1,8 @@ using RoyalCode.Extensions.PropertySelection; -using RoyalCode.SmartSearch.Abstractions; using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; internal sealed class CriterionResolution { diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/DefaultSpecifierFunctionGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/DefaultSpecifierFunctionGenerator.cs similarity index 99% rename from src/RoyalCode.SmartSearch.Linq/Filter/DefaultSpecifierFunctionGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/DefaultSpecifierFunctionGenerator.cs index 16506b3..31a4ff8 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/DefaultSpecifierFunctionGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/DefaultSpecifierFunctionGenerator.cs @@ -1,11 +1,10 @@ using RoyalCode.Extensions.PropertySelection; -using RoyalCode.SmartSearch.Abstractions; using RoyalCode.SmartSearch.Core.Extensions; using System.Collections; using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/DefaultSpecifierGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/DefaultSpecifierGenerator.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Filter/DefaultSpecifierGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/DefaultSpecifierGenerator.cs index 30a9c18..c9ee9be 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/DefaultSpecifierGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/DefaultSpecifierGenerator.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/FilterHandler.cs similarity index 80% rename from src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/FilterHandler.cs index 4574366..2034a8b 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierHandler.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/FilterHandler.cs @@ -1,17 +1,18 @@ using RoyalCode.SmartSearch.Filtering; +using RoyalCode.SmartSearch.Linq.Services; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// -/// Default implementation of +/// Default implementation of /// that applies the filters to a , /// retrieving the filters specifiers from the , /// which in turn knows how to apply the filter specification to the query. /// /// /// The query model type. -public sealed class SpecifierHandler : ISpecifier +public sealed class FilterHandler : IFilterHandler where TModel : class { private readonly ISpecifierFactory factory; @@ -21,7 +22,7 @@ public sealed class SpecifierHandler : ISpecifier /// /// The specifier factory to create the specifiers. /// The query to apply the filters. - public SpecifierHandler(ISpecifierFactory factory, IQueryable query) + public FilterHandler(ISpecifierFactory factory, IQueryable query) { this.factory = factory; Query = query; diff --git a/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifier.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifier.cs new file mode 100644 index 0000000..7248499 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifier.cs @@ -0,0 +1,20 @@ +namespace RoyalCode.SmartSearch.Linq.Filtering; + +/// +/// Component that applies the filtering conditions to the query, +/// where the query object is a . +/// +/// The model of the . +/// The filter type. +public interface ISpecifier + where TModel : class + where TFilter : class +{ + /// + /// Applies the specified filter to the given query, returning a filtered result set. + /// + /// The initial query to which the filter will be applied. Cannot be null. + /// The filter criteria used to refine the query results. Cannot be null. + /// An containing the filtered results based on the provided criteria. + IQueryable Specify(IQueryable query, TFilter filter); +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierFunctionGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierFunctionGenerator.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierFunctionGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierFunctionGenerator.cs index 6b8fdb1..39340e3 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierFunctionGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierFunctionGenerator.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierGenerator.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierGenerator.cs index c5eeed3..e3facf7 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierGenerator.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierGeneratorOptions.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierGeneratorOptions.cs similarity index 96% rename from src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierGeneratorOptions.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierGeneratorOptions.cs index 6c2ffc4..8657b72 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierGeneratorOptions.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/ISpecifierGeneratorOptions.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/InternalSpecifier.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/InternalSpecifier.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Filter/InternalSpecifier.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/InternalSpecifier.cs index abab7ba..f22f3af 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/InternalSpecifier.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/InternalSpecifier.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; internal sealed class InternalSpecifier : ISpecifier where TModel : class diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/InternalSpecifierGeneratorOptions.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/InternalSpecifierGeneratorOptions.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Filter/InternalSpecifierGeneratorOptions.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/InternalSpecifierGeneratorOptions.cs index 258d291..36ea804 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/InternalSpecifierGeneratorOptions.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/InternalSpecifierGeneratorOptions.cs @@ -2,7 +2,7 @@ using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; internal sealed class InternalSpecifierGeneratorOptions : ISpecifierGeneratorOptions where TModel : class diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierFactory.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierFactory.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Filter/SpecifierFactory.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierFactory.cs index 693a23d..d9b7ae2 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierFactory.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierFactory.cs @@ -1,7 +1,8 @@ -using System.Diagnostics.CodeAnalysis; +using RoyalCode.SmartSearch.Linq.Services; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// @@ -26,7 +27,7 @@ public SpecifierFactory(SpecifiersMap specifiers, this.functionGenerator = functionGenerator; } - public ISpecifier? GetSpecifier() + public ISpecifier GetSpecifier() where TModel : class where TFilter : class { diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierGeneratorOptions.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierGeneratorOptions.cs similarity index 96% rename from src/RoyalCode.SmartSearch.Linq/Filter/SpecifierGeneratorOptions.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierGeneratorOptions.cs index 8aef7a7..ed780a3 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierGeneratorOptions.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierGeneratorOptions.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; internal static class SpecifierGeneratorOptions { diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierGeneratorPropertyOptions.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierGeneratorPropertyOptions.cs similarity index 96% rename from src/RoyalCode.SmartSearch.Linq/Filter/SpecifierGeneratorPropertyOptions.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierGeneratorPropertyOptions.cs index 6df4558..128a77e 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifierGeneratorPropertyOptions.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifierGeneratorPropertyOptions.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; #pragma warning disable S2326 diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifiersMap.cs b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifiersMap.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Filter/SpecifiersMap.cs rename to src/RoyalCode.SmartSearch.Linq/Filtering/SpecifiersMap.cs index beab3f9..718d48a 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/SpecifiersMap.cs +++ b/src/RoyalCode.SmartSearch.Linq/Filtering/SpecifiersMap.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Filtering; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/IRemovable.cs b/src/RoyalCode.SmartSearch.Linq/IRemovable.cs deleted file mode 100644 index 8c593fc..0000000 --- a/src/RoyalCode.SmartSearch.Linq/IRemovable.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace RoyalCode.SmartSearch.Linq; - -/// -/// Componente to provide a queryable and methods to remove entities from the database. -/// -/// The entity type. -[Obsolete] -public interface IRemovable - where TEntity : class -{ - /// - /// Remove all entities from the database filter by the queryable. - /// - /// The query to filter the entities to be removed. - void RemoveAll(IQueryable entities); - - /// - /// Remove all entities from the database filter by the queryable. - /// - /// The query to filter the entities to be removed. - /// The cancellation token. - /// A task that represents the asynchronous operation. - Task RemoveAllAsync(IQueryable entities, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs b/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs index 65f9550..52226ad 100644 --- a/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs +++ b/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs @@ -1,6 +1,6 @@ -using RoyalCode.SmartSearch.Linq.Filter; -using RoyalCode.SmartSearch.Linq.Selector; -using RoyalCode.SmartSearch.Linq.Sorter; +using RoyalCode.SmartSearch.Linq.Filtering; +using RoyalCode.SmartSearch.Linq.Mappings; +using RoyalCode.SmartSearch.Linq.Sortings; using System.Linq.Expressions; namespace RoyalCode.SmartSearch.Linq; diff --git a/src/RoyalCode.SmartSearch.Linq/ISorter.cs b/src/RoyalCode.SmartSearch.Linq/ISorter.cs deleted file mode 100644 index 03b8f59..0000000 --- a/src/RoyalCode.SmartSearch.Linq/ISorter.cs +++ /dev/null @@ -1,40 +0,0 @@ -using RoyalCode.SmartSearch.Linq.Sorter; - -namespace RoyalCode.SmartSearch.Linq; - -/// -/// -/// Interface of the component to apply the sort operation to a query. -/// -/// -/// It has a default implementation, , -/// which makes use of other abstract components to automate sorting. -/// -/// -/// The query model type. -public interface ISorter - where TModel : class -{ - /// - /// - /// Applies sorting to the - /// according to the definitions of . - /// - /// - /// The query to sort. - /// The soring definitions. - /// A ordered query. - IQueryable OrderBy(IQueryable query, IEnumerable sortings); - - /// - /// - /// Applies a default sort to the query so that it can be possible to execute the paged query. - /// - /// - /// Normally this default sorting is done on top of the Id. - /// - /// - /// The query to sort. - /// A ordered query. - IQueryable DefaultOrderBy(IQueryable query); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs b/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs deleted file mode 100644 index 0b71ab6..0000000 --- a/src/RoyalCode.SmartSearch.Linq/ISpecifier.cs +++ /dev/null @@ -1,15 +0,0 @@ -using RoyalCode.SmartSearch.Core; -using RoyalCode.SmartSearch.Filtering; - -namespace RoyalCode.SmartSearch.Linq; - -/// -/// Component that applies the filtering conditions to the query, -/// where the query object is a . -/// -/// The model of the . -/// The filter type. -public interface ISpecifier : ISpecifier - where TModel : class - where TFilter : class -{ } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/EnumSelectorPropertyConverter.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/EnumSelectorPropertyConverter.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/EnumSelectorPropertyConverter.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/EnumSelectorPropertyConverter.cs index 654cfaf..a7b0178 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/EnumSelectorPropertyConverter.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/EnumSelectorPropertyConverter.cs @@ -1,7 +1,7 @@ using RoyalCode.Extensions.PropertySelection; using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; internal sealed class EnumSelectorPropertyConverter : ISelectorPropertyConverter, ISelectorPropertyResolver { diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/EnumerableSelectorPropertyResolver.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/EnumerableSelectorPropertyResolver.cs similarity index 98% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/EnumerableSelectorPropertyResolver.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/EnumerableSelectorPropertyResolver.cs index 850347f..af9b71f 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/EnumerableSelectorPropertyResolver.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/EnumerableSelectorPropertyResolver.cs @@ -3,7 +3,7 @@ using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; internal sealed class EnumerableSelectorPropertyResolver : ISelectorPropertyResolver { diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectResolver.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectResolver.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectResolver.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectResolver.cs index 4bf84cd..9f20f4b 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectResolver.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectResolver.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; /// /// A component that solves the mapping between properties of two types to create a select expression diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectorPropertyConverter.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectorPropertyConverter.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectorPropertyConverter.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectorPropertyConverter.cs index 98298fd..af3885d 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectorPropertyConverter.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectorPropertyConverter.cs @@ -1,7 +1,8 @@ using RoyalCode.Extensions.PropertySelection; +using RoyalCode.SmartSearch.Linq.Mappings; using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectorPropertyResolver.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectorPropertyResolver.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectorPropertyResolver.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectorPropertyResolver.cs index 620659a..ca2e961 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/ISelectorPropertyResolver.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/ISelectorPropertyResolver.cs @@ -1,6 +1,6 @@ using RoyalCode.Extensions.PropertySelection; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/NullableSelectorPropertyConverter.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/NullableSelectorPropertyConverter.cs similarity index 96% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/NullableSelectorPropertyConverter.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/NullableSelectorPropertyConverter.cs index 5f40609..961893d 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/NullableSelectorPropertyConverter.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/NullableSelectorPropertyConverter.cs @@ -2,7 +2,7 @@ using RoyalCode.SmartSearch.Core.Extensions; using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; internal sealed class NullableSelectorPropertyConverter : ISelectorPropertyConverter, ISelectorPropertyResolver { diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/SelectResolution.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/SelectResolution.cs similarity index 88% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/SelectResolution.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/SelectResolution.cs index 1e24e13..5cbd888 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/SelectResolution.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/SelectResolution.cs @@ -1,6 +1,6 @@ using RoyalCode.Extensions.PropertySelection; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; /// /// It contains the resolved properties for creating a select expression of the . diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/SubSelectSelectorPropertyResolver.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/SubSelectSelectorPropertyResolver.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Selector/Converters/SubSelectSelectorPropertyResolver.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/Converters/SubSelectSelectorPropertyResolver.cs index cd96a28..8c8ed30 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/Converters/SubSelectSelectorPropertyResolver.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/Converters/SubSelectSelectorPropertyResolver.cs @@ -3,7 +3,7 @@ using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Selector.Converters; +namespace RoyalCode.SmartSearch.Linq.Mappings.Converters; internal sealed class SubSelectSelectorPropertyResolver : ISelectorPropertyResolver { diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/DefaultSelectorExpressionGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/DefaultSelectorExpressionGenerator.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Selector/DefaultSelectorExpressionGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/DefaultSelectorExpressionGenerator.cs index 2fdd9d2..87a71e5 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/DefaultSelectorExpressionGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/DefaultSelectorExpressionGenerator.cs @@ -1,10 +1,10 @@ using RoyalCode.Extensions.PropertySelection; -using RoyalCode.SmartSearch.Linq.Selector.Converters; +using RoyalCode.SmartSearch.Linq.Mappings.Converters; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/DefaultSelectorGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/DefaultSelectorGenerator.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Linq/Selector/DefaultSelectorGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/DefaultSelectorGenerator.cs index 396f902..3f8aaaa 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/DefaultSelectorGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/DefaultSelectorGenerator.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/ISelector.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/ISelector.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Linq/ISelector.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/ISelector.cs index 547ec08..b1dbc85 100644 --- a/src/RoyalCode.SmartSearch.Linq/ISelector.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/ISelector.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/ISelectorExpressionGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/ISelectorExpressionGenerator.cs similarity index 94% rename from src/RoyalCode.SmartSearch.Linq/Selector/ISelectorExpressionGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/ISelectorExpressionGenerator.cs index 15c9c32..e0d6051 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/ISelectorExpressionGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/ISelectorExpressionGenerator.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/ISelectorGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/ISelectorGenerator.cs similarity index 91% rename from src/RoyalCode.SmartSearch.Linq/Selector/ISelectorGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/ISelectorGenerator.cs index 4346857..1ac0118 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/ISelectorGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/ISelectorGenerator.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/InternalSelector.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/InternalSelector.cs similarity index 91% rename from src/RoyalCode.SmartSearch.Linq/Selector/InternalSelector.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/InternalSelector.cs index a98ea99..906755e 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/InternalSelector.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/InternalSelector.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; internal sealed class InternalSelector : ISelector where TEntity : class diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/SelectorFactory.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/SelectorFactory.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Linq/Selector/SelectorFactory.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/SelectorFactory.cs index c0b023d..7cfd2aa 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/SelectorFactory.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/SelectorFactory.cs @@ -1,8 +1,9 @@ -using System.Diagnostics.CodeAnalysis; +using RoyalCode.SmartSearch.Linq.Services; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// Default implementation of . diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/SelectorNotFoundException.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/SelectorNotFoundException.cs similarity index 88% rename from src/RoyalCode.SmartSearch.Linq/Selector/SelectorNotFoundException.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/SelectorNotFoundException.cs index c2d1d17..a804ac5 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/SelectorNotFoundException.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/SelectorNotFoundException.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// Exception thrown when a selector is not found. diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/SelectorsMap.cs b/src/RoyalCode.SmartSearch.Linq/Mappings/SelectorsMap.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Selector/SelectorsMap.cs rename to src/RoyalCode.SmartSearch.Linq/Mappings/SelectorsMap.cs index 2e3b716..2b3b02a 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/SelectorsMap.cs +++ b/src/RoyalCode.SmartSearch.Linq/Mappings/SelectorsMap.cs @@ -2,7 +2,7 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; -namespace RoyalCode.SmartSearch.Linq.Selector; +namespace RoyalCode.SmartSearch.Linq.Mappings; /// /// A class that maps the types of the entity and Dto to the selector (). diff --git a/src/RoyalCode.SmartSearch.Linq/SearchesServiceCollectionExtensions.cs b/src/RoyalCode.SmartSearch.Linq/SearchesServiceCollectionExtensions.cs index 4537104..22f9f76 100644 --- a/src/RoyalCode.SmartSearch.Linq/SearchesServiceCollectionExtensions.cs +++ b/src/RoyalCode.SmartSearch.Linq/SearchesServiceCollectionExtensions.cs @@ -1,7 +1,8 @@ using Microsoft.Extensions.DependencyInjection; -using RoyalCode.SmartSearch.Linq.Filter; -using RoyalCode.SmartSearch.Linq.Selector; -using RoyalCode.SmartSearch.Linq.Sorter; +using RoyalCode.SmartSearch.Linq.Filtering; +using RoyalCode.SmartSearch.Linq.Mappings; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; namespace RoyalCode.SmartSearch.Linq; diff --git a/src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs b/src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs new file mode 100644 index 0000000..11e1a02 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs @@ -0,0 +1,64 @@ +using RoyalCode.SmartSearch.Defaults; +using RoyalCode.SmartSearch.Linq.Sortings; +using RoyalCode.SmartSearch.Services; + +namespace RoyalCode.SmartSearch.Linq.Services; + +/// +/// Provides functionality to prepare queries based on specified criteria for a given entity type. +/// +/// +/// This class is designed to facilitate the creation of queries by applying filters, sorting, and +/// selection criteria to an underlying data source. +/// It uses various providers and factories to construct the query dynamically. +/// +/// +/// The type of the entity for which queries are prepared. Must be a reference type. +/// +public class CriteriaPerformer : ICriteriaPerformer + where TEntity : class +{ + private readonly IQueryableProvider queryableProvider; + private readonly ISpecifierFactory specifierFactory; + private readonly IOrderByProvider orderByProvider; + private readonly ISelectorFactory selectorFactory; + + /// + /// Creates a new instance of . + /// + /// The provider to get the queryable for the entity type. + /// The factory to create specifiers for the query. + /// The provider for ordering the results. + /// The factory for selecting results. + public CriteriaPerformer( + IQueryableProvider queryableProvider, + ISpecifierFactory specifierFactory, + IOrderByProvider orderByProvider, + ISelectorFactory selectorFactory) + { + this.queryableProvider = queryableProvider; + this.specifierFactory = specifierFactory; + this.orderByProvider = orderByProvider; + this.selectorFactory = selectorFactory; + } + + /// + public IPreparedQuery Prepare(CriteriaOptions options) + { + var queryable = queryableProvider.GetQueryable(options.TrackingEnabled); + var criteriaQuery = new CriteriaQuery(queryable, specifierFactory, orderByProvider, selectorFactory); + + foreach (var filter in options.Filters) + criteriaQuery.Specify(filter); + + criteriaQuery.OrderBy(options.Sortings); + criteriaQuery.SetPageSkipTakeCount( + options.GetPageNumber(), + options.GetSkipCount(), + options.GetTakeCount(), + options.UseCount, + options.LastCount); + + return criteriaQuery; + } +} diff --git a/src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs b/src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs new file mode 100644 index 0000000..8500a43 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs @@ -0,0 +1,183 @@ +using RoyalCode.SmartSearch.Filtering; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; +using RoyalCode.SmartSearch.Mappings; +using RoyalCode.SmartSearch.Services; + +namespace RoyalCode.SmartSearch.Linq; + +public abstract class CriteriaQuery : IPreparedQuery, IFilterHandler + where TEntity : class +{ + private readonly ISpecifierFactory specifierFactory; + private readonly IOrderByProvider orderByProvider; + private readonly ISelectorFactory selectorFactory; + + private IQueryable query; + private Func? countQuery; + + private int pageNumber; + private int skip; + private int take; + private bool count; + private int lastCount; + + /// + /// Initializes a new instance of the class, + /// providing the necessary components for building and executing queries with specified criteria. + /// + /// + /// This constructor is designed to facilitate the creation of complex queries by combining + /// filtering, ordering, and projection capabilities. Ensure that all dependencies are properly + /// initialized before passing them to this constructor. + /// + /// The source queryable representing the data set to query. + /// The factory used to create specifiers for filtering criteria. + /// The provider used to define ordering rules for the query results. + /// The factory used to create selectors for projecting query results. + protected CriteriaQuery( + IQueryable queryable, + ISpecifierFactory specifierFactory, + IOrderByProvider orderByProvider, + ISelectorFactory selectorFactory) + { + query = queryable; + this.specifierFactory = specifierFactory; + this.orderByProvider = orderByProvider; + this.selectorFactory = selectorFactory; + } + + /// + public void Specify(TFilter filter) + where TFilter : class + { + var specifier = specifierFactory.GetSpecifier(); + query = specifier.Specify(query, filter); + } + + internal void OrderBy(IReadOnlyList sortings) + { + var orderByBuilder = new OrderByBuilder(query); + + foreach (var sorting in sortings) + { + var handler = orderByProvider.GetHandler(sorting.OrderBy); + if (handler is null) + continue; + + orderByBuilder.CurrentDirection = sorting.Direction; + orderByBuilder = handler.Handle(orderByBuilder); + } + } + + internal void SetPageSkipTakeCount(int pageNumber, int skip, int take, bool count, int lastCount) + { + this.pageNumber = pageNumber; + this.skip = skip; + this.take = take; + this.count = count; + this.lastCount = lastCount; + } + + internal void UseCountQuery(Func countQuery) + { + this.countQuery = countQuery; + } + + /// + public IPreparedQuery Select(ISearchSelect select) + where TDto : class + { + var expression = select.SelectExpression ?? selectorFactory.Create().GetSelectExpression(); + var queryable = query.Select(expression); + var criteriaQuery = CreateCriteriaQuery(queryable, specifierFactory, orderByProvider, selectorFactory); + criteriaQuery.SetPageSkipTakeCount(pageNumber, skip, take, count, lastCount); + criteriaQuery.UseCountQuery(query.Count); + return criteriaQuery; + } + + /// + /// Creates a criteria query for filtering, ordering, and selecting data from the specified queryable source. + /// + /// The type of the data transfer object (DTO) being queried. Must be a reference type. + /// The queryable source to apply criteria to. Cannot be null. + /// The factory used to create specifiers for filtering the query. Cannot be null. + /// The provider used to define ordering rules for the query. Cannot be null. + /// The factory used to create selectors for projecting the query results. Cannot be null. + /// A representing the configured query. + protected abstract CriteriaQuery CreateCriteriaQuery( + IQueryable queryable, + ISpecifierFactory specifierFactory, + IOrderByProvider orderByProvider, + ISelectorFactory selectorFactory) + where TDto : class; + + + protected IQueryable GetQueryableWithSkip() + { + var queryable = query; + if (skip > 0) + queryable = queryable.Skip(skip); + return queryable; + } + + protected IQueryable GetQueryableWithSkipAndTake(bool count) + { + var queryable = GetQueryableWithSkip(); + if (take > 0) + queryable = queryable.Take(take + (count ? 1 : 0)); + return queryable; + } + + /// + public bool Exists() => GetQueryableWithSkip().Any(); + + /// + public abstract Task ExistsAsync(CancellationToken ct = default); + + /// + public TEntity? FirstOrDefault() + { + return GetQueryableWithSkip().FirstOrDefault(); + } + + /// + public abstract Task FirstOrDefaultAsync(CancellationToken ct = default); + + /// + public TEntity Single() + { + var entities = GetQueryableWithSkip().Take(2).ToList(); + if (entities.Count == 0) + throw new InvalidOperationException("No entity found matching the criteria."); + if (entities.Count > 1) + throw new InvalidOperationException("More than one entity found matching the criteria."); + return entities[0]; + } + + /// + public abstract Task SingleAsync(CancellationToken ct = default); + + public IReadOnlyList ToList() => GetQueryableWithSkipAndTake(false).ToList(); + + /// + public abstract Task> ToListAsync(CancellationToken ct); + + /// + public IResultList ToResultList() + { + throw new NotImplementedException(); + } + + /// + public Task> ToResultListAsync(CancellationToken ct) + { + throw new NotImplementedException(); + } + + /// + public Task> ToAsyncListAsync(CancellationToken ct) + { + throw new NotImplementedException(); + } +} diff --git a/src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs b/src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs similarity index 53% rename from src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs rename to src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs index 78697a0..8c3c8b5 100644 --- a/src/RoyalCode.SmartSearch.Linq/IQueryableProvider.cs +++ b/src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs @@ -1,15 +1,18 @@ -namespace RoyalCode.SmartSearch.Linq; +namespace RoyalCode.SmartSearch.Linq.Services; /// /// Component to provide a for an entity. /// /// The entity type. -public interface IQueryableProvider +public interface IQueryableProvider where TEntity : class { /// /// Get a new queryable for the entity. /// + /// + /// Defines whether the entities should be tracked by the context, session or unit of work. + /// /// An instance. - IQueryable GetQueryable(); + IQueryable GetQueryable(bool tracking); } diff --git a/src/RoyalCode.SmartSearch.Linq/Selector/ISelectorFactory.cs b/src/RoyalCode.SmartSearch.Linq/Services/ISelectorFactory.cs similarity index 85% rename from src/RoyalCode.SmartSearch.Linq/Selector/ISelectorFactory.cs rename to src/RoyalCode.SmartSearch.Linq/Services/ISelectorFactory.cs index 02c1ad8..6b310dd 100644 --- a/src/RoyalCode.SmartSearch.Linq/Selector/ISelectorFactory.cs +++ b/src/RoyalCode.SmartSearch.Linq/Services/ISelectorFactory.cs @@ -1,5 +1,6 @@ - -namespace RoyalCode.SmartSearch.Linq.Selector; +using RoyalCode.SmartSearch.Linq.Mappings; + +namespace RoyalCode.SmartSearch.Linq.Services; /// /// A factory to create diff --git a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierFactory.cs b/src/RoyalCode.SmartSearch.Linq/Services/ISpecifierFactory.cs similarity index 57% rename from src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierFactory.cs rename to src/RoyalCode.SmartSearch.Linq/Services/ISpecifierFactory.cs index a1e959c..c9edbce 100644 --- a/src/RoyalCode.SmartSearch.Linq/Filter/ISpecifierFactory.cs +++ b/src/RoyalCode.SmartSearch.Linq/Services/ISpecifierFactory.cs @@ -1,5 +1,6 @@ +using RoyalCode.SmartSearch.Linq.Filtering; -namespace RoyalCode.SmartSearch.Linq.Filter; +namespace RoyalCode.SmartSearch.Linq.Services; /// /// A factory to create for a given model type and filter type. @@ -11,14 +12,14 @@ public interface ISpecifierFactory /// Creates a new specifier for a given model type and filter type. /// /// - /// May be returned null if no specifier is configured for the model and filter, or throw an exception. + /// Will throw an exception if no specifier is configured for the model and filter. /// /// /// The model type. /// The filter type. - /// A new specifier or null if not exists. - /// Optional, if no specifier is configured for the model and filter. - ISpecifier? GetSpecifier() + /// A new specifier. + /// Thrown if no specifier is configured for the model and filter. + ISpecifier GetSpecifier() where TModel : class where TFilter : class; } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/DefaultSorter.cs b/src/RoyalCode.SmartSearch.Linq/Sorter/DefaultSorter.cs deleted file mode 100644 index 1e87bee..0000000 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/DefaultSorter.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Ignore Spelling: sortings - -using System.ComponentModel; -using RoyalCode.SmartSearch.Abstractions; - -namespace RoyalCode.SmartSearch.Linq.Sorter; - -/// -/// -/// Default implementation of using additional abstract componentes: -/// -/// -/// -/// : -/// Used to find . -/// -/// -/// : -/// Used to apply the "Order By" through the . -/// -/// -/// : -/// Receives the "Order By" expression and applies it to the query. -/// -/// -/// -/// The query source model type. -public sealed class DefaultSorter : ISorter - where TModel : class -{ - private readonly IOrderByProvider provider; - - /// - /// Creates a new sorter with the to get the handlers. - /// - /// The order by handlers provider. - public DefaultSorter(IOrderByProvider provider) - { - this.provider = provider; - } - - /// - public IQueryable OrderBy(IQueryable query, IEnumerable sortings) - { - var builder = new OrderByBuilder(query); - - foreach (var sorting in sortings) - { - var handler = provider.GetHandler(sorting.OrderBy); - if (handler is null) - continue; - - builder.CurrentDirection = sorting.Direction; - handler.Handle(builder); - } - - return builder.OrderedQueryable; - } - - /// - public IQueryable DefaultOrderBy(IQueryable query) - { - var handler = provider.GetDefaultHandler(); - var builder = new OrderByBuilder(query) - { - CurrentDirection = ListSortDirection.Ascending - }; - handler.Handle(builder); - return builder.OrderedQueryable; - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByBuilder.cs b/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByBuilder.cs deleted file mode 100644 index d7463c1..0000000 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Linq.Expressions; - -namespace RoyalCode.SmartSearch.Linq.Sorter; - -/// -/// -/// Component for applying the sort expressions to the query. -/// -/// -/// This component is called by the , which is liable for the expression. -/// -/// -/// The query source model type. -public interface IOrderByBuilder -{ - /// - /// Applies the sort expression to the query. - /// - /// The sort expression. - /// Selected property type. - void Add(Expression> keySelector); -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/DefaultOrderByGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/DefaultOrderByGenerator.cs similarity index 94% rename from src/RoyalCode.SmartSearch.Linq/Sorter/DefaultOrderByGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/DefaultOrderByGenerator.cs index 4230107..afbcc71 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/DefaultOrderByGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/DefaultOrderByGenerator.cs @@ -1,7 +1,7 @@ using RoyalCode.Extensions.PropertySelection; using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; /// /// Default implementation of , using for diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByGenerator.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByGenerator.cs similarity index 93% rename from src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByGenerator.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByGenerator.cs index 42c80a7..e9f0c75 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByGenerator.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByGenerator.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByHandler.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByHandler.cs similarity index 88% rename from src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByHandler.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByHandler.cs index 54c8bc9..3116c06 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByHandler.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByHandler.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; /// /// @@ -21,5 +21,5 @@ public interface IOrderByHandler /// Use the to add a sort expression to the query. /// /// Used to apply the expressions to the query. - void Handle(IOrderByBuilder builder); + OrderByBuilder Handle(OrderByBuilder builder); } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByProvider.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByProvider.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByProvider.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByProvider.cs index ba49fda..0bec5b1 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/IOrderByProvider.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/IOrderByProvider.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; /// /// diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/InvalidOrderByExpressionException.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/InvalidOrderByExpressionException.cs similarity index 92% rename from src/RoyalCode.SmartSearch.Linq/Sorter/InvalidOrderByExpressionException.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/InvalidOrderByExpressionException.cs index f05f76e..db7cf3f 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/InvalidOrderByExpressionException.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/InvalidOrderByExpressionException.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; /// /// Exception thrown when the expression is not valid for the . diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByBuilder.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs similarity index 68% rename from src/RoyalCode.SmartSearch.Linq/Sorter/OrderByBuilder.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs index 35b2edc..117b4a4 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByBuilder.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs @@ -1,15 +1,15 @@ using System.ComponentModel; using System.Linq.Expressions; -using RoyalCode.SmartSearch.Abstractions; -namespace RoyalCode.SmartSearch.Linq.Sorter; +#pragma warning disable S3358 // ternary operator + +namespace RoyalCode.SmartSearch.Linq.Sortings; /// -/// Internal implementation of . -/// Used by the . +/// Builder for "Order By" clauses in a LINQ query. /// /// -internal sealed class OrderByBuilder : IOrderByBuilder +public ref struct OrderByBuilder { private readonly IQueryable query; private IOrderedQueryable? ordered; @@ -27,19 +27,16 @@ public OrderByBuilder(IQueryable query) /// /// The of the current . /// - /// - /// Used internally by . - /// /// internal ListSortDirection CurrentDirection { get; set; } /// /// Returns the ordered query. /// - internal IQueryable OrderedQueryable => ordered ?? query; + internal readonly IQueryable OrderedQueryable => ordered ?? query; /// - public void Add(Expression> keySelector) + public OrderByBuilder Add(Expression> keySelector) { ordered = CurrentDirection == ListSortDirection.Ascending ? ordered is null @@ -48,5 +45,7 @@ public void Add(Expression> keySelector) : ordered is null ? query.OrderByDescending(keySelector) : ordered.ThenByDescending(keySelector); + + return this; } } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByHandler.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByHandler.cs similarity index 90% rename from src/RoyalCode.SmartSearch.Linq/Sorter/OrderByHandler.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/OrderByHandler.cs index 6a84a8f..7a845c0 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByHandler.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByHandler.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; internal sealed class OrderByHandler : IOrderByHandler where TModel : class @@ -14,7 +14,7 @@ public OrderByHandler(Expression> expression) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Handle(IOrderByBuilder builder) => builder.Add(expression); + public OrderByBuilder Handle(OrderByBuilder builder) => builder.Add(expression); } internal static class OrderByHandler @@ -36,7 +36,7 @@ public static IOrderByHandler Create(Expression expression) var propertyType = delegateGenerics[1]; var handlerType = typeof(OrderByHandler<,>).MakeGenericType(modelType, propertyType); - var handler = handlerType.GetConstructors().First().Invoke(new[] { expression }); + var handler = handlerType.GetConstructors().First().Invoke([expression]); return (IOrderByHandler)handler; } } diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByHandlersMap.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByHandlersMap.cs similarity index 97% rename from src/RoyalCode.SmartSearch.Linq/Sorter/OrderByHandlersMap.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/OrderByHandlersMap.cs index ebabc29..adb3b0e 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByHandlersMap.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByHandlersMap.cs @@ -2,7 +2,7 @@ using System.Linq.Expressions; using System.Runtime.CompilerServices; -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; internal sealed class OrderByHandlersMap { diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByNotSupportedException.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByNotSupportedException.cs similarity index 92% rename from src/RoyalCode.SmartSearch.Linq/Sorter/OrderByNotSupportedException.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/OrderByNotSupportedException.cs index f718df2..290353a 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByNotSupportedException.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByNotSupportedException.cs @@ -1,5 +1,4 @@ - -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; /// /// Exception thrown when the order by is not supported for the type. diff --git a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByProvider.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs similarity index 95% rename from src/RoyalCode.SmartSearch.Linq/Sorter/OrderByProvider.cs rename to src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs index 169b5a2..78e78e1 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sorter/OrderByProvider.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs @@ -1,4 +1,4 @@ -namespace RoyalCode.SmartSearch.Linq.Sorter; +namespace RoyalCode.SmartSearch.Linq.Sortings; internal sealed class OrderByProvider : IOrderByProvider { diff --git a/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs b/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs index 7490fff..ca31706 100644 --- a/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using RoyalCode.SmartSearch.Abstractions; using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Filter; +using RoyalCode.SmartSearch.Linq.Filtering; using System.Collections; using System.Linq.Expressions; From b7231b52189b5f5e2aa1fea24e825b7f7b790a52 Mon Sep 17 00:00:00 2001 From: Glauco Knihs Date: Wed, 25 Jun 2025 16:06:49 -0300 Subject: [PATCH 4/9] refacto completed. --- .../AsyncResultList.cs | 4 +- .../IResultList.cs | 2 +- .../ResultList.cs | 6 +- .../AllEntitiesPipeline.cs | 196 ------------ .../Configurations/ISearchConfigurations.cs | 3 +- .../Configurations/SearchConfigurations.cs | 41 +-- ...workSearchesServiceCollectionExtensions.cs | 34 +- .../Internals/IAllEntities.cs | 18 -- .../Internals/IPipelineFactory.cs | 16 - .../Internals/ISearch.cs | 18 -- .../Internals/InternalAllEntities.cs | 11 - .../Internals/InternalSearch.cs | 11 - .../PipelineFactory.cs | 68 ---- .../QueryableProvider.cs | 42 --- .../Removable.cs | 28 -- .../SearchManager.cs | 38 --- .../SearchPipeline.cs | 289 ----------------- .../SearchPipelineBase.cs | 100 ------ .../Services/CriteriaPerformer.cs | 35 +- .../Services/CriteriaQuery.cs | 298 ++++++++++++++++++ .../Services/ICriteriaPerformer.cs | 19 ++ .../{ => Services}/ISearchManager.cs | 3 +- .../Services/InternalCriteria.cs | 11 + .../Services/SearchManager.cs | 61 ++++ .../Services/CriteriaQuery.cs | 183 ----------- .../Services/IQueryableProvider.cs | 18 -- .../Sortings/DefaultSorting.cs | 25 ++ .../Sortings/OrderByBuilder.cs | 4 +- .../Sortings/OrderByProvider.cs | 6 +- .../AllEntitiesTests.cs | 56 ++-- .../SelectorExpressionGeneratorTests.cs | 2 +- .../SortingTests.cs | 1 - .../SpecifierFunctionGeneratorTests.cs | 6 +- 33 files changed, 515 insertions(+), 1138 deletions(-) delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Internals/IAllEntities.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Internals/IPipelineFactory.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Internals/ISearch.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalAllEntities.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalSearch.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Removable.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/SearchManager.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs delete mode 100644 src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs rename src/{RoyalCode.SmartSearch.Linq => RoyalCode.SmartSearch.EntityFramework}/Services/CriteriaPerformer.cs (67%) create mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaQuery.cs create mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Services/ICriteriaPerformer.cs rename src/RoyalCode.SmartSearch.EntityFramework/{ => Services}/ISearchManager.cs (84%) create mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Services/InternalCriteria.cs create mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Services/SearchManager.cs delete mode 100644 src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs delete mode 100644 src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs create mode 100644 src/RoyalCode.SmartSearch.Linq/Sortings/DefaultSorting.cs diff --git a/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs index 9c971d9..27d5f11 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/AsyncResultList.cs @@ -23,10 +23,10 @@ public sealed class AsyncResultList : IAsyncResultList public int Pages { get; init; } /// - public int Skipped { get; } + public int Skipped { get; init; } /// - public int Taken { get; } + public int Taken { get; init; } /// [JsonConverter(typeof(SortingsConverter))] diff --git a/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs index 924f1e2..a937f77 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/IResultList.cs @@ -52,7 +52,7 @@ public interface IResultList /// /// Projections carried out during the research. /// - Dictionary Projections { get; } + Dictionary? Projections { get; } } /// diff --git a/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs b/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs index 05d093e..88ad04a 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ResultList.cs @@ -25,17 +25,17 @@ public class ResultList : IResultList public int Pages { get; init; } /// - public int Skipped { get; } + public int Skipped { get; init; } /// - public int Taken { get; } + public int Taken { get; init; } /// [JsonConverter(typeof(SortingsConverter))] public IReadOnlyList Sortings { get; init; } = null!; /// - public Dictionary Projections { get; init; } = null!; + public Dictionary? Projections { get; init; } = null!; /// public IReadOnlyList Items { get; init; } = null!; diff --git a/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs b/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs deleted file mode 100644 index 1a5b661..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/AllEntitiesPipeline.cs +++ /dev/null @@ -1,196 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Core.Pipeline; -using RoyalCode.SmartSearch.Defaults; -using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Services; -using System.Runtime.CompilerServices; - -namespace RoyalCode.SmartSearch.EntityFramework; - -/// -/// Default implementation of . -/// -/// The entity type -public sealed class AllEntitiesPipeline : SearchPipelineBase, IAllEntitiesPipeline - where TEntity : class -{ - /// - public AllEntitiesPipeline( - IQueryableProvider queryableProvider, - ISpecifierFactory specifierFactory, - ISorter sorter) - : base(queryableProvider, specifierFactory, sorter) - { } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Any(CriteriaOptions searchCriteria) - { - return PrepareQuery(searchCriteria).Any(); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task AnyAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) - { - return PrepareQuery(searchCriteria).AnyAsync(cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ICollection Execute(CriteriaOptions searchCriteria) - { - return PrepareQuery(searchCriteria).ToList(); - } - - /// - public async Task> ExecuteAsync( - CriteriaOptions searchCriteria, - CancellationToken cancellationToken = default) - { - return await PrepareQuery(searchCriteria).ToListAsync(cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TEntity? First(CriteriaOptions searchCriteria) - { - return PrepareQuery(searchCriteria).FirstOrDefault(); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task FirstAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) - { - return PrepareQuery(searchCriteria).FirstOrDefaultAsync(cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveAll(CriteriaOptions searchCriteria) - { - var query = PrepareQuery(searchCriteria); - var removable = queryableProvider.GetRemovable(); - removable.RemoveAll(query); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task RemoveAllAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) - { - var query = PrepareQuery(searchCriteria); - var removable = queryableProvider.GetRemovable(); - return removable.RemoveAllAsync(query, cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TEntity Single(CriteriaOptions searchCriteria) - { - return PrepareQuery(searchCriteria).Single(); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task SingleAsync(CriteriaOptions searchCriteria, CancellationToken cancellationToken = default) - { - return PrepareQuery(searchCriteria).SingleAsync(cancellationToken); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll(CriteriaOptions searchCriteria, Action updateAction) - { - var query = PrepareQuery(searchCriteria); - foreach(var entity in query) - { - updateAction(entity); - } - } - - /// - public async Task UpdateAllAsync( - CriteriaOptions searchCriteria, - Action updateAction, - CancellationToken cancellationToken = default) - { - var query = PrepareQuery(searchCriteria); - await foreach (var entity in query.AsAsyncEnumerable().WithCancellation(cancellationToken)) - { - updateAction(entity); - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UpdateAll(CriteriaOptions searchCriteria, TData data, Action updateAction) - { - var query = PrepareQuery(searchCriteria); - foreach(var entity in query) - { - updateAction(entity, data); - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public async Task UpdateAllAsync( - CriteriaOptions searchCriteria, - TData data, - Action updateAction, - CancellationToken cancellationToken = default) - { - var query = PrepareQuery(searchCriteria); - await foreach (var entity in query.AsAsyncEnumerable().WithCancellation(cancellationToken)) - { - updateAction(entity, data); - } - } - - /// - public void UpdateAll( - CriteriaOptions searchCriteria, - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction) - where TData : class - { - var query = PrepareQuery(searchCriteria); - foreach (var entity in query) - { - var entityId = entityIdGet(entity); - var data = collection.FirstOrDefault(d => Equals(dataIdGet(d), entityId)); - if (data is not null) - updateAction(entity, data); - else - throw new ArgumentOutOfRangeException( - nameof(collection), - $"The collection does not contain any data with the id '{entityId}'"); - } - } - - /// - public async Task UpdateAllAsync( - CriteriaOptions searchCriteria, - ICollection collection, - Func entityIdGet, - Func dataIdGet, - Action updateAction, - CancellationToken cancellationToken = default) - where TData : class - { - var query = PrepareQuery(searchCriteria); - await foreach (var entity in query.AsAsyncEnumerable().WithCancellation(cancellationToken)) - { - var entityId = entityIdGet(entity); - var data = collection.FirstOrDefault(d => Equals(dataIdGet(d), entityId)); - if (data is not null) - updateAction(entity, data); - else - throw new ArgumentOutOfRangeException( - nameof(collection), - $"The collection does not contain any data with the id '{entityId}'"); - } - } -} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs index 88caa04..a1caa0d 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs @@ -11,7 +11,8 @@ public interface ISearchConfigurations : ISearchConfigurations where TDbContext : DbContext { /// - /// Add a search for an entity as a service, related to used by the unit of work. + /// Add a for an entity as a service, + /// related to used by the unit of work. /// /// The entity type. /// The same instance. diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs index 8a05b59..15ab1c2 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs @@ -1,9 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using RoyalCode.SmartSearch.Abstractions; -using RoyalCode.SmartSearch.EntityFramework.Internals; -using RoyalCode.SmartSearch.Linq; +using RoyalCode.SmartSearch.EntityFramework.Services; namespace RoyalCode.SmartSearch.EntityFramework.Configurations; @@ -20,43 +17,27 @@ public sealed class SearchConfigurations : ISearchConfigurations, PipelineFactory>(); - services.TryAddTransient, SearchManager>(); } /// public ISearchConfigurations Add() where TEntity : class { - // add search as a service for the respective context - var searchType = typeof(ISearch<>).MakeGenericType(typeof(TEntity)); - var dbSearchType = typeof(Internals.ISearch<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); - var searchImplType = typeof(InternalSearch<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); - - services.Add(ServiceDescriptor.Describe( - dbSearchType, - searchImplType, - ServiceLifetime.Transient)); + // add criteria as a service for the respective context + var serviceType = typeof(ICriteria<>).MakeGenericType(typeof(TEntity)); + var implType = typeof(InternalCriteria<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); services.Add(ServiceDescriptor.Describe( - searchType, - sp => sp.GetService(dbSearchType)!, + serviceType, + implType, ServiceLifetime.Transient)); - // add all entities as a service for the respective context - var allType = typeof(IAllEntities<>).MakeGenericType(typeof(TEntity)); - var dbAllType = typeof(IAllEntities<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); - var allImplType = typeof(InternalAllEntities<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); - - services.Add(ServiceDescriptor.Describe( - dbAllType, - allImplType, - ServiceLifetime.Transient)); + // add criteria performer as a service for the respective context + serviceType = typeof(ICriteriaPerformer<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); + implType = typeof(CriteriaPerformer<,>).MakeGenericType(typeof(TDbContext), typeof(TEntity)); services.Add(ServiceDescriptor.Describe( - allType, - sp => sp.GetService(dbAllType)!, + serviceType, + implType, ServiceLifetime.Transient)); return this; diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs index 720d6f3..8fea001 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs @@ -1,10 +1,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection.Extensions; -using RoyalCode.SmartSearch.Abstractions; -using RoyalCode.SmartSearch.EntityFramework; +using RoyalCode.SmartSearch; +using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.EntityFramework.Configurations; -using RoyalCode.SmartSearch.EntityFramework.Internals; +using RoyalCode.SmartSearch.EntityFramework.Services; using RoyalCode.SmartSearch.Linq; using RoyalCode.SmartSearch.Linq.Services; using RoyalCode.SmartSearch.Linq.Sortings; @@ -18,12 +18,12 @@ public static class EntityFrameworkSearchesServiceCollectionExtensions { /// /// - /// Add services for and + /// Add services for /// using entity framework and defining the for performing the searches. /// /// /// You can also use a to create - /// and + /// /// of entities related to the . /// /// @@ -40,18 +40,20 @@ public static IServiceCollection AddEntityFrameworkSearches(this ISe { ArgumentNullException.ThrowIfNull(configureAction); + services.AddSearchManager(); + configureAction(new SearchConfigurations(services)); return services; } /// /// - /// Adds services to work with and + /// Adds services to work with and /// using the entity framework. /// /// /// It will be necessary to use or - /// to create and + /// to create and /// of entities related to the . /// /// @@ -61,8 +63,10 @@ public static IServiceCollection AddEntityFrameworkSearches(this ISe public static IServiceCollection AddSearchManager(this IServiceCollection services) where TDbContext : DbContext { + if (services.Any(d => d.ServiceType == typeof(ISearchManager))) + return services; + services.AddSmartSearchLinq(); - services.TryAddTransient, PipelineFactory>(); services.TryAddTransient, SearchManager>(); services.TryAddTransient>(); @@ -71,24 +75,28 @@ public static IServiceCollection AddSearchManager(this IServiceColle /// /// - /// Creates a new for the entity + /// Creates a new for the entity /// using the used by the unit of work. /// /// /// /// /// - public static ISearch Search(this DbContext db) + public static ICriteria Criteria(this DbContext db) where TEntity : class { var specifierFactory = db.GetService(); var orderByFactory = db.GetService(); var selectorFactory = db.GetService(); - var pipelineFacotry = new PipelineFactory(db, specifierFactory, orderByFactory, selectorFactory); + var preparer = new CriteriaPerformer( + db, + specifierFactory, + orderByFactory, + selectorFactory); - var search = new InternalSearch(pipelineFacotry); + var criteria = new Criteria(preparer); - return search; + return criteria; } } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Internals/IAllEntities.cs b/src/RoyalCode.SmartSearch.EntityFramework/Internals/IAllEntities.cs deleted file mode 100644 index 627e09c..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/Internals/IAllEntities.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Abstractions; - -#pragma warning disable S2326 // Unused type parameters should be removed - -namespace RoyalCode.SmartSearch.EntityFramework.Internals; - -/// -/// -/// Represents a search for all entities of a specific type using the Entity Framework Core. -/// -/// -/// The type of the database context. -/// The type of the entity. -public interface IAllEntities : IAllEntities - where TEntity : class - where TDbContext : DbContext -{ } \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Internals/IPipelineFactory.cs b/src/RoyalCode.SmartSearch.EntityFramework/Internals/IPipelineFactory.cs deleted file mode 100644 index d791c03..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/Internals/IPipelineFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Core.Pipeline; - -#pragma warning disable S2326 // Unused type parameters should be removed - -namespace RoyalCode.SmartSearch.EntityFramework.Internals; - -/// -/// Factory to create search pipelines and searches for all entities using a specific . -/// -/// -/// The type to use for the search pipelines and searches for all entities. -/// -public interface IPipelineFactory : IPipelineFactory - where TDbContext : DbContext -{ } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Internals/ISearch.cs b/src/RoyalCode.SmartSearch.EntityFramework/Internals/ISearch.cs deleted file mode 100644 index 274b897..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/Internals/ISearch.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Abstractions; - -#pragma warning disable S2326 // Unused type parameters should be removed - -namespace RoyalCode.SmartSearch.EntityFramework.Internals; - -/// -/// -/// Represents a search for a specific entity type using the Entity Framework Core. -/// -/// -/// The type of the database context. -/// The type of the entity. -public interface ISearch : ISearch - where TEntity : class - where TDbContext : DbContext -{ } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalAllEntities.cs b/src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalAllEntities.cs deleted file mode 100644 index f3750de..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalAllEntities.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Core.Pipeline; - -namespace RoyalCode.SmartSearch.EntityFramework.Internals; - -internal sealed class InternalAllEntities : AllEntities, IAllEntities - where TEntity : class - where TDbContext : DbContext -{ - public InternalAllEntities(IPipelineFactory factory) : base(factory) { } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalSearch.cs b/src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalSearch.cs deleted file mode 100644 index 594a3e3..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/Internals/InternalSearch.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Core.Pipeline; - -namespace RoyalCode.SmartSearch.EntityFramework.Internals; - -internal sealed class InternalSearch : Search, ISearch - where TEntity : class - where TDbContext : DbContext -{ - public InternalSearch(IPipelineFactory factory) : base(factory) { } -} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs b/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs deleted file mode 100644 index 445b77e..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/PipelineFactory.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.OperationHint.Abstractions; -using RoyalCode.SmartSearch.Core.Pipeline; -using RoyalCode.SmartSearch.EntityFramework.Internals; -using RoyalCode.SmartSearch.Linq.Services; -using RoyalCode.SmartSearch.Linq.Sortings; - -namespace RoyalCode.SmartSearch.EntityFramework; - -/// -public sealed class PipelineFactory : IPipelineFactory - where TDbContext : DbContext -{ - private readonly TDbContext db; - private readonly ISpecifierFactory specifierFactory; - private readonly IOrderByProvider orderByProvider; - private readonly ISelectorFactory selectorFactory; - private readonly IHintPerformer? hintPerformer; - - /// - /// - /// Create a new instance of . - /// - /// - /// The database context. - /// The specifier factory. - /// The order by provider. - /// The selector factory. - /// Optional, the hint performer. - public PipelineFactory( - TDbContext db, - ISpecifierFactory specifierFactory, - IOrderByProvider orderByProvider, - ISelectorFactory selectorFactory, - IHintPerformer? hintPerformer = null) - { - this.db = db; - this.specifierFactory = specifierFactory; - this.orderByProvider = orderByProvider; - this.selectorFactory = selectorFactory; - this.hintPerformer = hintPerformer; - } - - /// - public ISearchPipeline Create() where TEntity : class - { - var queryableProvider = new QueryableProvider(db); - var sorter = new DefaultSorter(orderByProvider); - return new SearchPipeline(queryableProvider, specifierFactory, sorter); - } - - /// - public ISearchPipeline Create() where TEntity : class where TDto : class - { - var queryableProvider = new QueryableProvider(db); - var sorter = new DefaultSorter(orderByProvider); - var selector = selectorFactory.Create(); - return new SearchPipeline(queryableProvider, specifierFactory, sorter, selector); - } - - /// - public IAllEntitiesPipeline CreateAllEntities() where TEntity : class - { - var queryableProvider = new QueryableProvider(db, true, hintPerformer); - var sorter = new DefaultSorter(orderByProvider); - return new AllEntitiesPipeline(queryableProvider, specifierFactory, sorter); - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs b/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs deleted file mode 100644 index 7f00bb7..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/QueryableProvider.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.OperationHint.Abstractions; -using RoyalCode.SmartSearch.Linq.Services; - -namespace RoyalCode.SmartSearch.EntityFramework; - -/// -internal sealed class QueryableProvider : IQueryableProvider - where TDbContext : DbContext - where TEntity : class -{ - private readonly TDbContext db; - private readonly bool tracking; - private readonly IHintPerformer? hintPerformer; - - public QueryableProvider(TDbContext db, bool tracking = false, IHintPerformer? hintPerformer = null) - { - this.db = db; - this.tracking = tracking; - this.hintPerformer = hintPerformer; - } - - /// - public IQueryable GetQueryable() - { - if (tracking) - { - IQueryable query = db.Set(); - return hintPerformer is null - ? query - : hintPerformer.Perform(query); - } - - return db.Set().AsNoTracking(); - } - - /// - public IRemovable GetRemovable() - { - return new Removable(db); - } -} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Removable.cs b/src/RoyalCode.SmartSearch.EntityFramework/Removable.cs deleted file mode 100644 index 41b7238..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/Removable.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Linq; - -namespace RoyalCode.SmartSearch.EntityFramework; - -/// -internal sealed class Removable : IRemovable - where TEntity : class -{ - private readonly DbContext db; - - public Removable(DbContext db) - { - this.db = db; - } - - /// - public void RemoveAll(IQueryable entities) - { - db.RemoveRange(entities); - } - - /// - public async Task RemoveAllAsync(IQueryable entities, CancellationToken cancellationToken = default) - { - db.RemoveRange(await entities.ToListAsync(cancellationToken: cancellationToken)); - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchManager.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchManager.cs deleted file mode 100644 index 857fee6..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchManager.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Abstractions; -using RoyalCode.SmartSearch.EntityFramework.Internals; - -#pragma warning disable S2326 // Unused type parameters should be removed - -namespace RoyalCode.SmartSearch.EntityFramework; - -/// -/// Default implementation for . -/// -/// -public sealed class SearchManager : ISearchManager - where TDbContext : DbContext -{ - private readonly IPipelineFactory pipelineFactory; - - /// - /// Creates a new search manager with the pipeline factory for the . - /// - /// - public SearchManager(IPipelineFactory pipelineFactory) - { - this.pipelineFactory = pipelineFactory; - } - - /// - public IAllEntities All() where TEntity : class - { - return new InternalAllEntities(pipelineFactory); - } - - /// - public ISearch Search() where TEntity : class - { - return new InternalSearch(pipelineFactory); - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs deleted file mode 100644 index e81a49f..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipeline.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Defaults; -using RoyalCode.SmartSearch.Linq.Mappings; -using RoyalCode.SmartSearch.Linq.Services; - -namespace RoyalCode.SmartSearch.EntityFramework; - -#pragma warning disable S3358 // ifs ternaries should not be nested - -/// -/// -/// Default implementation of . -/// -/// -/// The entity type. -public sealed class SearchPipeline : SearchPipelineBase, ISearchPipeline - where TEntity : class -{ - /// - public SearchPipeline( - IQueryableProvider queryableProvider, - ISpecifierFactory specifierFactory, - ISorter sorter) - : base( - queryableProvider, - specifierFactory, - sorter) - { } - - /// - public IResultList Execute(CriteriaOptions criteria) - { - var sortedQuery = PrepareQuery(criteria); - - var executableQuery = criteria.Paginate - ? sortedQuery - .Skip(criteria.GetSkipCount()) - .Take(criteria.ItemsPerPage + 1) - : sortedQuery; - - var list = executableQuery.ToList(); - var hasNextPage = list.Count > criteria.ItemsPerPage; - var items = hasNextPage && criteria.Paginate ? list.Take(criteria.ItemsPerPage).ToList() : list; - - var count = criteria.LastCount > 0 - ? criteria.LastCount - : criteria.UseCount - ? hasNextPage - ? sortedQuery.Count() - : criteria.GetSkipCount() + list.Count - : 0; - - var pages = CountPages(count, criteria.ItemsPerPage); - - var result = new ResultList() - { - Page = criteria.GetPageNumber(), - Count = count, - ItemsPerPage = criteria.ItemsPerPage, - Pages = pages, - Sortings = criteria.Sortings, - Projections = [], - Items = items - }; - - return result; - } - - /// - public async Task> ExecuteAsync(CriteriaOptions criteria, CancellationToken token) - { - var sortedQuery = PrepareQuery(criteria); - - var executableQuery = criteria.Paginate - ? sortedQuery - .Skip(criteria.GetSkipCount()) - .Take(criteria.ItemsPerPage + 1) - : sortedQuery; - - var list = await executableQuery.ToListAsync(token); - var hasNextPage = list.Count > criteria.ItemsPerPage; - var items = hasNextPage && criteria.Paginate ? list.Take(criteria.ItemsPerPage).ToList() : list; - - var count = criteria.LastCount > 0 - ? criteria.LastCount - : criteria.UseCount - ? hasNextPage - ? await sortedQuery.CountAsync(token) - : criteria.GetSkipCount() + list.Count - : 0; - - var pages = CountPages(count, criteria.ItemsPerPage); - - var result = new ResultList() - { - Page = criteria.GetPageNumber(), - Count = count, - ItemsPerPage = criteria.ItemsPerPage, - Pages = pages, - Sortings = criteria.Sortings, - Projections = [], - Items = items - }; - - return result; - } - - /// - public async Task> AsyncExecuteAsync(CriteriaOptions criteria, CancellationToken token) - { - var sortedQuery = PrepareQuery(criteria); - - var executableQuery = criteria.Paginate - ? sortedQuery - .Skip(criteria.GetSkipCount()) - .Take(criteria.ItemsPerPage) - : sortedQuery; - - var items = executableQuery.AsAsyncEnumerable(); - - var count = criteria.LastCount > 0 - ? criteria.LastCount - : criteria.UseCount - ? await sortedQuery.CountAsync(token) - : 0; - - var pages = CountPages(count, criteria.ItemsPerPage); - - var result = new AsyncResultList() - { - Page = criteria.GetPageNumber(), - Count = count, - ItemsPerPage = criteria.ItemsPerPage, - Pages = pages, - Sortings = criteria.Sortings, - Projections = [], - Items = items - }; - - return result; - } -} - -/// -/// -/// Default implementation of that selects a DTO from the entity. -/// -/// -/// The entity type. -/// The selected DTO type. -public sealed class SearchPipeline : SearchPipelineBase, ISearchPipeline - where TEntity : class - where TDto : class -{ - private readonly ISelector? selector; - - /// - public SearchPipeline( - IQueryableProvider queryableProvider, - ISpecifierFactory specifierFactory, - ISorter sorter, - ISelector? selector = null) - : base( - queryableProvider, - specifierFactory, - sorter) - { - this.selector = selector; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private IQueryable Select(IQueryable query, CriteriaOptions criteria) - { - var selectExpression = (Expression>?)criteria.Select?.SelectExpression - ?? selector?.GetSelectExpression() - ?? throw new SelectorNotFoundException(typeof(TEntity), typeof(TDto)); - - return query.Select(selectExpression); - } - - /// - public IResultList Execute(CriteriaOptions criteria) - { - var sortedQuery = PrepareQuery(criteria); - var selectQuery = Select(sortedQuery, criteria); - var executableQuery = criteria.Paginate - ? selectQuery.Skip(criteria.GetSkipCount()).Take(criteria.ItemsPerPage + 1) - : selectQuery; - - var list = executableQuery.ToList(); - var hasNextPage = list.Count > criteria.ItemsPerPage; - var items = hasNextPage && criteria.Paginate ? list.Take(criteria.ItemsPerPage).ToList() : list; - - var count = criteria.LastCount > 0 - ? criteria.LastCount - : criteria.UseCount - ? hasNextPage - ? sortedQuery.Count() - : criteria.GetSkipCount() + list.Count - : 0; - - var pages = CountPages(count, criteria.ItemsPerPage); - - var result = new ResultList() - { - Page = criteria.GetPageNumber(), - Count = count, - ItemsPerPage = criteria.ItemsPerPage, - Pages = pages, - Sortings = criteria.Sortings, - Projections = [], - Items = items - }; - - return result; - } - - /// - public async Task> ExecuteAsync(CriteriaOptions criteria, CancellationToken token) - { - var sortedQuery = PrepareQuery(criteria); - var selectQuery = Select(sortedQuery, criteria); - var executableQuery = criteria.Paginate - ? selectQuery.Skip(criteria.GetSkipCount()).Take(criteria.ItemsPerPage + 1) - : selectQuery; - - var list = await executableQuery.ToListAsync(token); - var hasNextPage = list.Count > criteria.ItemsPerPage; - var items = hasNextPage && criteria.Paginate ? list.Take(criteria.ItemsPerPage).ToList() : list; - - var count = criteria.LastCount > 0 - ? criteria.LastCount - : criteria.UseCount - ? hasNextPage - ? await sortedQuery.CountAsync(token) - : criteria.GetSkipCount() + list.Count - : 0; - - var pages = CountPages(count, criteria.ItemsPerPage); - - var result = new ResultList() - { - Page = criteria.GetPageNumber(), - Count = count, - ItemsPerPage = criteria.ItemsPerPage, - Pages = pages, - Sortings = criteria.Sortings, - Projections = [], - Items = items - }; - - return result; - } - - /// - public async Task> AsyncExecuteAsync(CriteriaOptions criteria, CancellationToken token) - { - var sortedQuery = PrepareQuery(criteria); - var selectQuery = Select(sortedQuery, criteria); - var executableQuery = criteria.Paginate - ? selectQuery.Skip(criteria.GetSkipCount()).Take(criteria.ItemsPerPage) - : selectQuery; - - var items = executableQuery.AsAsyncEnumerable(); - - var count = criteria.LastCount > 0 - ? criteria.LastCount - : criteria.UseCount - ? await sortedQuery.CountAsync(token) - : 0; - - var pages = CountPages(count, criteria.ItemsPerPage); - - var result = new AsyncResultList() - { - Page = criteria.GetPageNumber(), - Count = count, - ItemsPerPage = criteria.ItemsPerPage, - Pages = pages, - Sortings = criteria.Sortings, - Projections = [], - Items = items - }; - - return result; - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs b/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs deleted file mode 100644 index e01baab..0000000 --- a/src/RoyalCode.SmartSearch.EntityFramework/SearchPipelineBase.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Runtime.CompilerServices; -using RoyalCode.SmartSearch.Core.Pipeline; -using RoyalCode.SmartSearch.Defaults; -using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Filter; -using RoyalCode.SmartSearch.Linq.Filtering; -using RoyalCode.SmartSearch.Linq.Services; - -namespace RoyalCode.SmartSearch.EntityFramework; - -/// -/// -/// A base implementation for to share commons methods/operations. -/// -/// -/// The query source model type. -public abstract class SearchPipelineBase - where TEntity : class -{ - /// - /// - /// The component that will provide the to be used in the pipeline. - /// - /// - protected readonly IQueryableProvider queryableProvider; - - /// - /// - /// Factory to create the to be used in the pipeline. - /// - /// - protected readonly ISpecifierFactory specifierFactory; - - /// - /// - /// Component to apply the order by instructions. - /// - /// - protected readonly ISorter sorter; - - /// - /// Creates a new search pipeline that receives the dependencies. - /// - /// Provides the query. - /// Provides the specifiers. - /// Sort the query. - protected SearchPipelineBase( - IQueryableProvider queryableProvider, - ISpecifierFactory specifierFactory, - ISorter sorter) - { - this.queryableProvider = queryableProvider; - this.specifierFactory = specifierFactory; - this.sorter = sorter; - } - - /// - /// Creates the query, apply the filters and sort. - /// - /// Search criteria. - /// A prepared query. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected IQueryable PrepareQuery(CriteriaOptions criteria) - { - var baseQuery = queryableProvider.GetQueryable(); - - var queryFilters = criteria.Filters; - if (queryFilters.Count is not 0) - { - var handler = new FilterHandler(specifierFactory, baseQuery); - foreach (var searchFilter in queryFilters) - { - searchFilter.ApplyFilter(handler); - } - - baseQuery = handler.Query; - } - - if (criteria.Sortings.Count is not 0) - return sorter.OrderBy(baseQuery, criteria.Sortings); - - return criteria.Paginate - ? sorter.DefaultOrderBy(baseQuery) - : baseQuery; - } - - /// - /// Count the pages. - /// - /// The record count. - /// How many items is listed per page. - /// The total number of pages. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static int CountPages(int count, int itemsPerPage) - { - return count == 0 || itemsPerPage < 1 - ? 0 - : (int)Math.Floor((double)count / itemsPerPage); - } -} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs similarity index 67% rename from src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs rename to src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs index 11e1a02..36d94d9 100644 --- a/src/RoyalCode.SmartSearch.Linq/Services/CriteriaPerformer.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs @@ -1,8 +1,10 @@ -using RoyalCode.SmartSearch.Defaults; +using Microsoft.EntityFrameworkCore; +using RoyalCode.SmartSearch.Defaults; +using RoyalCode.SmartSearch.Linq.Services; using RoyalCode.SmartSearch.Linq.Sortings; using RoyalCode.SmartSearch.Services; -namespace RoyalCode.SmartSearch.Linq.Services; +namespace RoyalCode.SmartSearch.EntityFramework.Services; /// /// Provides functionality to prepare queries based on specified criteria for a given entity type. @@ -12,31 +14,35 @@ namespace RoyalCode.SmartSearch.Linq.Services; /// selection criteria to an underlying data source. /// It uses various providers and factories to construct the query dynamically. /// +/// +/// The type of the used to access the database. +/// /// /// The type of the entity for which queries are prepared. Must be a reference type. /// -public class CriteriaPerformer : ICriteriaPerformer +public sealed class CriteriaPerformer : ICriteriaPerformer + where TDbContext : DbContext where TEntity : class { - private readonly IQueryableProvider queryableProvider; + private readonly TDbContext db; private readonly ISpecifierFactory specifierFactory; private readonly IOrderByProvider orderByProvider; private readonly ISelectorFactory selectorFactory; /// - /// Creates a new instance of . + /// Creates a new instance of . /// - /// The provider to get the queryable for the entity type. + /// The to get the queryable for the entity type. /// The factory to create specifiers for the query. /// The provider for ordering the results. /// The factory for selecting results. public CriteriaPerformer( - IQueryableProvider queryableProvider, + TDbContext db, ISpecifierFactory specifierFactory, IOrderByProvider orderByProvider, ISelectorFactory selectorFactory) { - this.queryableProvider = queryableProvider; + this.db = db; this.specifierFactory = specifierFactory; this.orderByProvider = orderByProvider; this.selectorFactory = selectorFactory; @@ -45,11 +51,18 @@ public CriteriaPerformer( /// public IPreparedQuery Prepare(CriteriaOptions options) { - var queryable = queryableProvider.GetQueryable(options.TrackingEnabled); - var criteriaQuery = new CriteriaQuery(queryable, specifierFactory, orderByProvider, selectorFactory); + var entities = options.TrackingEnabled + ? db.Set() + : db.Set().AsNoTracking(); + + var criteriaQuery = new CriteriaQuery( + entities, + specifierFactory, + orderByProvider, + selectorFactory); foreach (var filter in options.Filters) - criteriaQuery.Specify(filter); + filter.ApplyFilter(criteriaQuery); criteriaQuery.OrderBy(options.Sortings); criteriaQuery.SetPageSkipTakeCount( diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaQuery.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaQuery.cs new file mode 100644 index 0000000..2f5a1ac --- /dev/null +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaQuery.cs @@ -0,0 +1,298 @@ +using Microsoft.EntityFrameworkCore; +using RoyalCode.SmartSearch.Filtering; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; +using RoyalCode.SmartSearch.Mappings; +using RoyalCode.SmartSearch.Services; +using System.Runtime.CompilerServices; + +#pragma warning disable S3358 // ifs ternaries should not be nested + +namespace RoyalCode.SmartSearch.EntityFramework.Services; + +/// +/// +/// Represents a query builder for constructing and executing queries +/// with filtering, sorting, pagination, and projection capabilities for a specified entity type. +/// +/// +/// +/// +/// The class provides a flexible and extensible way to build +/// queries by combining filtering criteria, sorting rules, pagination settings, and projection expressions. +///
+/// It is designed to work with LINQ and supports both synchronous and asynchronous query execution. +///
+/// This class is particularly useful for scenarios where dynamic query construction is required, +/// such as implementing search functionality or handling complex data retrieval requirements. +///
+/// +/// Thread safety: Instances of this class are not guaranteed to be thread-safe. +/// If multiple threads need to access the same instance, synchronization mechanisms should be used. +/// +///
+/// The type of the entity being queried. Must be a reference type. +public class CriteriaQuery : IPreparedQuery, IFilterHandler + where TEntity : class +{ + private readonly ISpecifierFactory specifierFactory; + private readonly IOrderByProvider orderByProvider; + private readonly ISelectorFactory selectorFactory; + + private IQueryable query; + + private int pageNumber; + private int skip; + private int take; + private bool count; + private int lastCount; + + private readonly List appliedSorting = []; + + /// + /// Initializes a new instance of the class, + /// providing the necessary components for building and executing queries with specified criteria. + /// + /// + /// This constructor is designed to facilitate the creation of complex queries by combining + /// filtering, ordering, and projection capabilities. Ensure that all dependencies are properly + /// initialized before passing them to this constructor. + /// + /// The initial queryable instance representing the data source for the entity type. + /// The factory used to create specifiers for filtering criteria. + /// The provider used to define ordering rules for the query results. + /// The factory used to create selectors for projecting query results. + public CriteriaQuery( + IQueryable query, + ISpecifierFactory specifierFactory, + IOrderByProvider orderByProvider, + ISelectorFactory selectorFactory) + { + this.query = query; + this.specifierFactory = specifierFactory; + this.orderByProvider = orderByProvider; + this.selectorFactory = selectorFactory; + } + + /// + public void Specify(TFilter filter) + where TFilter : class + { + var specifier = specifierFactory.GetSpecifier(); + query = specifier.Specify(query, filter); + } + + internal void OrderBy(IReadOnlyList sortings) + { + var orderByBuilder = new OrderByBuilder(query); + + foreach (var sorting in sortings) + { + var handler = orderByProvider.GetHandler(sorting.OrderBy); + if (handler is null) + continue; + + appliedSorting.Add(sorting); + + orderByBuilder.CurrentDirection = sorting.Direction; + orderByBuilder = handler.Handle(orderByBuilder); + } + + query = orderByBuilder.OrderedQueryable; + + CheckSorting(); + } + + internal void SetPageSkipTakeCount(int pageNumber, int skip, int take, bool count, int lastCount) + { + this.pageNumber = pageNumber; + this.skip = skip; + this.take = take; + this.count = count; + this.lastCount = lastCount; + + CheckSorting(); + } + + /// + public IPreparedQuery Select(ISearchSelect select) + where TDto : class + { + var expression = select.SelectExpression ?? selectorFactory.Create().GetSelectExpression(); + var queryable = query.Select(expression); + var criteriaQuery = new CriteriaQuery( + queryable, + specifierFactory, + orderByProvider, + selectorFactory); + + criteriaQuery.SetPageSkipTakeCount(pageNumber, skip, take, count, lastCount); + return criteriaQuery; + } + + private void CheckSorting() + { + if (appliedSorting.Count is 0 && skip > 0) + { + var orderByBuilder = new OrderByBuilder(query); + var handler = orderByProvider.GetDefaultHandler(); + orderByBuilder = handler.Handle(orderByBuilder); + query = orderByBuilder.OrderedQueryable; + appliedSorting.Add(DefaultSorting.Instance); + } + } + + private IQueryable GetQueryableWithSkip() + { + var queryable = query; + if (skip > 0) + queryable = queryable.Skip(skip); + return queryable; + } + + private IQueryable GetQueryableWithSkipAndTake(bool count) + { + var queryable = GetQueryableWithSkip(); + if (take > 0) + queryable = queryable.Take(take + (count ? 1 : 0)); + return queryable; + } + + private int CountPages(int count) + { + return count == 0 || take < 1 + ? 0 + : (int)Math.Floor((double)count / take); + } + + /// + public bool Exists() + => GetQueryableWithSkip().Any(); + + /// + public Task ExistsAsync(CancellationToken ct = default) + => GetQueryableWithSkip().AnyAsync(ct); + + /// + public TEntity? FirstOrDefault() + => GetQueryableWithSkip().FirstOrDefault(); + + /// + public Task FirstOrDefaultAsync(CancellationToken ct = default) + => GetQueryableWithSkip().FirstOrDefaultAsync(ct); + + /// + public TEntity Single() + => GetQueryableWithSkip().Single(); + + /// + public Task SingleAsync(CancellationToken ct = default) + => GetQueryableWithSkip().SingleAsync(ct); + + /// + public IReadOnlyList ToList() => GetQueryableWithSkipAndTake(false).ToList(); + + /// + public async Task> ToListAsync(CancellationToken ct) + => await GetQueryableWithSkipAndTake(false).ToListAsync(ct); + + /// + public IResultList ToResultList() + { + var executableQuery = GetQueryableWithSkipAndTake(true); + + var items = executableQuery.ToList(); + var hasNextPage = items.Count > take; + if (hasNextPage) + items = items.Take(take).ToList(); + + var queryCount = lastCount > 0 + ? lastCount + : count + ? hasNextPage + ? query.Count() + : skip + items.Count + : 0; + + var pages = CountPages(queryCount); + + var result = new ResultList() + { + Page = pageNumber, + Count = queryCount, + ItemsPerPage = take, + Pages = pages, + Skipped = skip, + Taken = hasNextPage ? take : items.Count, + Sortings = appliedSorting, + Items = items + }; + + return result; + } + + /// + public async Task> ToResultListAsync(CancellationToken ct) + { + var executableQuery = GetQueryableWithSkipAndTake(true); + + var items = await executableQuery.ToListAsync(ct); + var hasNextPage = items.Count > take; + if (hasNextPage) + items = items.Take(take).ToList(); + + var queryCount = lastCount > 0 + ? lastCount + : count + ? hasNextPage + ? await query.CountAsync(ct) + : skip + items.Count + : 0; + + var pages = CountPages(queryCount); + + var result = new ResultList() + { + Page = pageNumber, + Count = queryCount, + ItemsPerPage = take, + Pages = pages, + Skipped = skip, + Taken = hasNextPage ? take : items.Count, + Sortings = appliedSorting, + Items = items + }; + + return result; + } + + /// + public async Task> ToAsyncListAsync(CancellationToken ct) + { + var executableQuery = GetQueryableWithSkipAndTake(false); + + var queryCount = lastCount > 0 + ? lastCount + : count + ? await query.CountAsync(ct) + : 0; + + var items = executableQuery.AsAsyncEnumerable(); + + var pages = CountPages(queryCount); + + var result = new AsyncResultList() + { + Page = pageNumber, + Count = queryCount, + ItemsPerPage = take, + Pages = pages, + Skipped = skip, + Taken = take, + Sortings = appliedSorting, + Items = items + }; + + return result; + } +} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Services/ICriteriaPerformer.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/ICriteriaPerformer.cs new file mode 100644 index 0000000..de63158 --- /dev/null +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/ICriteriaPerformer.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using RoyalCode.SmartSearch.Services; + +namespace RoyalCode.SmartSearch.EntityFramework.Services; + +/// +/// Defines functionality for performing criteria-based operations on entities within a specific database context. +/// +/// +/// The type of the database context, which must derive from . +/// +/// +/// The type of the entity on which criteria-based operations are performed, which must be a reference type. +/// +public interface ICriteriaPerformer : ICriteriaPerformer + where TDbContext : DbContext + where TEntity : class +{ +} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/ISearchManager.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/ISearchManager.cs similarity index 84% rename from src/RoyalCode.SmartSearch.EntityFramework/ISearchManager.cs rename to src/RoyalCode.SmartSearch.EntityFramework/Services/ISearchManager.cs index 3fe95c1..a5b9813 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/ISearchManager.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/ISearchManager.cs @@ -1,9 +1,8 @@ using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Abstractions; #pragma warning disable S2326 // Unused type parameters should be removed -namespace RoyalCode.SmartSearch.EntityFramework; +namespace RoyalCode.SmartSearch.EntityFramework.Services; /// /// diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Services/InternalCriteria.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/InternalCriteria.cs new file mode 100644 index 0000000..ff23743 --- /dev/null +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/InternalCriteria.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; +using RoyalCode.SmartSearch.Defaults; + +namespace RoyalCode.SmartSearch.EntityFramework.Services; + +internal sealed class InternalCriteria : Criteria + where TDbContext : DbContext + where TEntity : class +{ + public InternalCriteria(ICriteriaPerformer performer) : base(performer) { } +} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Services/SearchManager.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/SearchManager.cs new file mode 100644 index 0000000..d3b81ae --- /dev/null +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/SearchManager.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; + +#pragma warning disable S2326 // Unused type parameters should be removed + +namespace RoyalCode.SmartSearch.EntityFramework.Services; + +/// +/// Default implementation for . +/// +/// +public sealed class SearchManager : ISearchManager + where TDbContext : DbContext +{ + private readonly TDbContext db; + private readonly ISpecifierFactory specifierFactory; + private readonly IOrderByProvider orderByProvider; + private readonly ISelectorFactory selectorFactory; + + /// + /// Initializes a new instance of the class, + /// providing the necessary dependencies for performing database searches. + /// + /// + /// The database context used to query the underlying data source. Must not be null. + /// + /// + /// The factory responsible for creating specifiers that define search criteria. Must not be null. + /// + /// + /// The provider used to specify ordering rules for search results. Must not be null. + /// + /// + /// The factory responsible for creating selectors that determine which fields to include in the search results. + /// Must not be null. + /// + public SearchManager( + TDbContext db, + ISpecifierFactory specifierFactory, + IOrderByProvider orderByProvider, + ISelectorFactory selectorFactory) + { + this.db = db; + this.specifierFactory = specifierFactory; + this.orderByProvider = orderByProvider; + this.selectorFactory = selectorFactory; + } + + /// + public ICriteria Criteria() where TEntity : class + { + var performer = new CriteriaPerformer( + db, + specifierFactory, + orderByProvider, + selectorFactory); + + return new InternalCriteria(performer); + } +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs b/src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs deleted file mode 100644 index 8500a43..0000000 --- a/src/RoyalCode.SmartSearch.Linq/Services/CriteriaQuery.cs +++ /dev/null @@ -1,183 +0,0 @@ -using RoyalCode.SmartSearch.Filtering; -using RoyalCode.SmartSearch.Linq.Services; -using RoyalCode.SmartSearch.Linq.Sortings; -using RoyalCode.SmartSearch.Mappings; -using RoyalCode.SmartSearch.Services; - -namespace RoyalCode.SmartSearch.Linq; - -public abstract class CriteriaQuery : IPreparedQuery, IFilterHandler - where TEntity : class -{ - private readonly ISpecifierFactory specifierFactory; - private readonly IOrderByProvider orderByProvider; - private readonly ISelectorFactory selectorFactory; - - private IQueryable query; - private Func? countQuery; - - private int pageNumber; - private int skip; - private int take; - private bool count; - private int lastCount; - - /// - /// Initializes a new instance of the class, - /// providing the necessary components for building and executing queries with specified criteria. - /// - /// - /// This constructor is designed to facilitate the creation of complex queries by combining - /// filtering, ordering, and projection capabilities. Ensure that all dependencies are properly - /// initialized before passing them to this constructor. - /// - /// The source queryable representing the data set to query. - /// The factory used to create specifiers for filtering criteria. - /// The provider used to define ordering rules for the query results. - /// The factory used to create selectors for projecting query results. - protected CriteriaQuery( - IQueryable queryable, - ISpecifierFactory specifierFactory, - IOrderByProvider orderByProvider, - ISelectorFactory selectorFactory) - { - query = queryable; - this.specifierFactory = specifierFactory; - this.orderByProvider = orderByProvider; - this.selectorFactory = selectorFactory; - } - - /// - public void Specify(TFilter filter) - where TFilter : class - { - var specifier = specifierFactory.GetSpecifier(); - query = specifier.Specify(query, filter); - } - - internal void OrderBy(IReadOnlyList sortings) - { - var orderByBuilder = new OrderByBuilder(query); - - foreach (var sorting in sortings) - { - var handler = orderByProvider.GetHandler(sorting.OrderBy); - if (handler is null) - continue; - - orderByBuilder.CurrentDirection = sorting.Direction; - orderByBuilder = handler.Handle(orderByBuilder); - } - } - - internal void SetPageSkipTakeCount(int pageNumber, int skip, int take, bool count, int lastCount) - { - this.pageNumber = pageNumber; - this.skip = skip; - this.take = take; - this.count = count; - this.lastCount = lastCount; - } - - internal void UseCountQuery(Func countQuery) - { - this.countQuery = countQuery; - } - - /// - public IPreparedQuery Select(ISearchSelect select) - where TDto : class - { - var expression = select.SelectExpression ?? selectorFactory.Create().GetSelectExpression(); - var queryable = query.Select(expression); - var criteriaQuery = CreateCriteriaQuery(queryable, specifierFactory, orderByProvider, selectorFactory); - criteriaQuery.SetPageSkipTakeCount(pageNumber, skip, take, count, lastCount); - criteriaQuery.UseCountQuery(query.Count); - return criteriaQuery; - } - - /// - /// Creates a criteria query for filtering, ordering, and selecting data from the specified queryable source. - /// - /// The type of the data transfer object (DTO) being queried. Must be a reference type. - /// The queryable source to apply criteria to. Cannot be null. - /// The factory used to create specifiers for filtering the query. Cannot be null. - /// The provider used to define ordering rules for the query. Cannot be null. - /// The factory used to create selectors for projecting the query results. Cannot be null. - /// A representing the configured query. - protected abstract CriteriaQuery CreateCriteriaQuery( - IQueryable queryable, - ISpecifierFactory specifierFactory, - IOrderByProvider orderByProvider, - ISelectorFactory selectorFactory) - where TDto : class; - - - protected IQueryable GetQueryableWithSkip() - { - var queryable = query; - if (skip > 0) - queryable = queryable.Skip(skip); - return queryable; - } - - protected IQueryable GetQueryableWithSkipAndTake(bool count) - { - var queryable = GetQueryableWithSkip(); - if (take > 0) - queryable = queryable.Take(take + (count ? 1 : 0)); - return queryable; - } - - /// - public bool Exists() => GetQueryableWithSkip().Any(); - - /// - public abstract Task ExistsAsync(CancellationToken ct = default); - - /// - public TEntity? FirstOrDefault() - { - return GetQueryableWithSkip().FirstOrDefault(); - } - - /// - public abstract Task FirstOrDefaultAsync(CancellationToken ct = default); - - /// - public TEntity Single() - { - var entities = GetQueryableWithSkip().Take(2).ToList(); - if (entities.Count == 0) - throw new InvalidOperationException("No entity found matching the criteria."); - if (entities.Count > 1) - throw new InvalidOperationException("More than one entity found matching the criteria."); - return entities[0]; - } - - /// - public abstract Task SingleAsync(CancellationToken ct = default); - - public IReadOnlyList ToList() => GetQueryableWithSkipAndTake(false).ToList(); - - /// - public abstract Task> ToListAsync(CancellationToken ct); - - /// - public IResultList ToResultList() - { - throw new NotImplementedException(); - } - - /// - public Task> ToResultListAsync(CancellationToken ct) - { - throw new NotImplementedException(); - } - - /// - public Task> ToAsyncListAsync(CancellationToken ct) - { - throw new NotImplementedException(); - } -} diff --git a/src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs b/src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs deleted file mode 100644 index 8c3c8b5..0000000 --- a/src/RoyalCode.SmartSearch.Linq/Services/IQueryableProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace RoyalCode.SmartSearch.Linq.Services; - -/// -/// Component to provide a for an entity. -/// -/// The entity type. -public interface IQueryableProvider - where TEntity : class -{ - /// - /// Get a new queryable for the entity. - /// - /// - /// Defines whether the entities should be tracked by the context, session or unit of work. - /// - /// An instance. - IQueryable GetQueryable(bool tracking); -} diff --git a/src/RoyalCode.SmartSearch.Linq/Sortings/DefaultSorting.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/DefaultSorting.cs new file mode 100644 index 0000000..2220545 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/DefaultSorting.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; + +namespace RoyalCode.SmartSearch.Linq.Sortings; + +/// +/// Represents the default sorting configuration, typically by the "Id" property in ascending order. +/// +public sealed class DefaultSorting : ISorting +{ + /// + /// The default property name used for sorting. + /// + public const string DefaultOrderBy = "Id"; + + /// + /// Singleton instance of . + /// + public static readonly DefaultSorting Instance = new(); + + /// + public string OrderBy => DefaultOrderBy; + + /// + public ListSortDirection Direction => ListSortDirection.Ascending; +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs index 117b4a4..2630a13 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByBuilder.cs @@ -28,12 +28,12 @@ public OrderByBuilder(IQueryable query) /// The of the current . /// /// - internal ListSortDirection CurrentDirection { get; set; } + public ListSortDirection CurrentDirection { get; set; } /// /// Returns the ordered query. /// - internal readonly IQueryable OrderedQueryable => ordered ?? query; + public readonly IQueryable OrderedQueryable => ordered ?? query; /// public OrderByBuilder Add(Expression> keySelector) diff --git a/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs index 78e78e1..766af5d 100644 --- a/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs +++ b/src/RoyalCode.SmartSearch.Linq/Sortings/OrderByProvider.cs @@ -11,7 +11,9 @@ public OrderByProvider(OrderByHandlersMap handlers, IOrderByGenerator? generator this.generator = generator; } - public IOrderByHandler GetDefaultHandler() where TModel : class => GetHandler("Id")!; + public IOrderByHandler GetDefaultHandler() + where TModel : class + => GetHandler(DefaultSorting.DefaultOrderBy)!; public IOrderByHandler? GetHandler(string orderBy) where TModel : class @@ -26,4 +28,4 @@ public OrderByProvider(OrderByHandlersMap handlers, IOrderByGenerator? generator handlers.Add((typeof(TModel), orderBy), handler); return handler; } -} \ No newline at end of file +} diff --git a/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs b/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs index 01f02e7..c98f984 100644 --- a/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs @@ -1,8 +1,6 @@ - -using FluentAssertions; +using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using RoyalCode.SmartSearch.Abstractions; namespace RoyalCode.SmartSearch.Tests; @@ -39,10 +37,10 @@ public void Must_CollectAll_WhenNoFilterOrSorting() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act - ICollection result = all.Collect(); + IReadOnlyList result = all.Collect(); // assert result.Should().HaveCount(3); @@ -62,10 +60,10 @@ public async Task Must_CollectAll_WhenNoFilterOrSortingAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act - ICollection result = await all.CollectAsync(); + IReadOnlyList result = await all.CollectAsync(); // assert result.Should().HaveCount(3); @@ -85,11 +83,11 @@ public void Must_CollectOne_WhenFilterByName() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; - ICollection result = all.FilterBy(filter).Collect(); + IReadOnlyList result = all.FilterBy(filter).Collect(); // assert result.Should().HaveCount(1); @@ -110,11 +108,11 @@ public async Task Must_CollectOne_WhenFilterByNameAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; - ICollection result = await all.FilterBy(filter).CollectAsync(); + IReadOnlyList result = await all.FilterBy(filter).CollectAsync(); // assert result.Should().HaveCount(1).And.ContainSingle(x => x.Id == 2); @@ -134,7 +132,7 @@ public void Must_Exists_WhenFilterByName() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; @@ -158,7 +156,7 @@ public async Task Must_Exists_WhenFilterByNameAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; @@ -182,7 +180,7 @@ public void Must_NotExists_WhenFilterByName() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "D" }; @@ -206,7 +204,7 @@ public async Task Must_NotExists_WhenFilterByNameAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "D" }; @@ -230,11 +228,11 @@ public void Must_First_WhenFilterByName() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; - SimpleModel? result = all.FilterBy(filter).First(); + SimpleModel? result = all.FilterBy(filter).FirstOrDefault(); // assert @@ -256,11 +254,11 @@ public async Task Must_First_WhenFilterByNameAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; - SimpleModel? result = await all.FilterBy(filter).FirstAsync(); + SimpleModel? result = await all.FilterBy(filter).FirstDefaultAsync(); // assert result.Should().NotBeNull().And.Match(x => x.Id == 2); @@ -280,11 +278,11 @@ public void Must_FirstBeNull_WhenFilterByName() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "D" }; - SimpleModel? result = all.FilterBy(filter).First(); + SimpleModel? result = all.FilterBy(filter).FirstOrDefault(); // assert result.Should().BeNull(); @@ -304,11 +302,11 @@ public async Task Must_FirstBeNull_WhenFilterByNameAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "D" }; - SimpleModel? result = await all.FilterBy(filter).FirstAsync(); + SimpleModel? result = await all.FilterBy(filter).FirstDefaultAsync(); // assert result.Should().BeNull(); @@ -328,7 +326,7 @@ public void Must_Single_WhenFilterByName() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; @@ -352,7 +350,7 @@ public async Task Must_Single_WhenFilterByNameAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "B" }; @@ -376,7 +374,7 @@ public void Must_Throw_WhenSingle_HasMoreThenOne() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act Action act = () => all.Single(); @@ -399,7 +397,7 @@ public async Task Must_Throw_WhenSingle_HasMoreThenOneAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act Func act = () => all.SingleAsync(); @@ -422,7 +420,7 @@ public void Must_Throw_WhenSingle_HasNoOne() context.Add(new SimpleModel { Id = 3, Name = "C" }); context.SaveChanges(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "D" }; @@ -446,7 +444,7 @@ public async Task Must_Throw_WhenSingle_HasNoOneAsync() context.Add(new SimpleModel { Id = 3, Name = "C" }); await context.SaveChangesAsync(); - var all = scope.ServiceProvider.GetRequiredService>(); + var all = scope.ServiceProvider.GetRequiredService>(); // act var filter = new SimpleFilter { Name = "D" }; diff --git a/src/RoyalCode.SmartSearch.Tests/SelectorExpressionGeneratorTests.cs b/src/RoyalCode.SmartSearch.Tests/SelectorExpressionGeneratorTests.cs index bba0129..af05898 100644 --- a/src/RoyalCode.SmartSearch.Tests/SelectorExpressionGeneratorTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/SelectorExpressionGeneratorTests.cs @@ -1,4 +1,4 @@ -using RoyalCode.SmartSearch.Linq.Selector; +using RoyalCode.SmartSearch.Linq.Mappings; namespace RoyalCode.SmartSearch.Tests; diff --git a/src/RoyalCode.SmartSearch.Tests/SortingTests.cs b/src/RoyalCode.SmartSearch.Tests/SortingTests.cs index 53c9123..408a19c 100644 --- a/src/RoyalCode.SmartSearch.Tests/SortingTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/SortingTests.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using RoyalCode.SmartSearch.Abstractions; using System.ComponentModel; using System.Text.Json; diff --git a/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs b/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs index ca31706..1ff120d 100644 --- a/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs @@ -1,7 +1,5 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.Extensions.DependencyInjection; -using RoyalCode.SmartSearch.Abstractions; using RoyalCode.SmartSearch.Linq; using RoyalCode.SmartSearch.Linq.Filtering; using System.Collections; @@ -52,14 +50,14 @@ public void Generate_Must_GenerateTheFilter_When_ConfiguredWithDbContext() ServiceProvider provider = services.BuildServiceProvider(); // act - var search = provider.GetService>(); + var search = provider.GetRequiredService>(); // assert Assert.NotNull(search); // act search!.FilterBy(new SimpleFilter()); - var resultList = search.ToList(); + var resultList = search.AsSearch().ToList(); // assert Assert.NotNull(resultList); From 9d3403237b7fa63130c3c5f7875f85163f108ce9 Mon Sep 17 00:00:00 2001 From: Glauco Knihs Date: Wed, 25 Jun 2025 22:58:17 -0300 Subject: [PATCH 5/9] Update Searches library version and refactor query classes - Update `Directory.Build.props`: Changed `SearchesPreview` to `-preview-2`, updated `LibTargets` and `AspTargets` for `net8` and `net9`, and set `SearchesVer` to `0.8.0`. - Refactor `CriteriaPerformer.cs`: Modified `CriteriaPerformer` to inherit from `CriteriaPerformerBase`, updated the constructor, removed private fields, and refactored the `Prepare` method for better organization. - Create `CriteriaPerformerBase.cs`: Introduced an abstract class to encapsulate common query preparation functionality, including a `Prepare` method and an abstract `GetQueryable` method for derived classes. --- src/Directory.Build.props | 2 +- .../Services/CriteriaPerformer.cs | 36 +------- .../Services/CriteriaPerformerBase.cs | 87 +++++++++++++++++++ 3 files changed, 92 insertions(+), 33 deletions(-) create mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformerBase.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e05c1d5..1d2af71 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 0.8.0 - -preview-1 + -preview-2 8.0.2 diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs index 36d94d9..f20c90b 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformer.cs @@ -1,8 +1,6 @@ using Microsoft.EntityFrameworkCore; -using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.Linq.Services; using RoyalCode.SmartSearch.Linq.Sortings; -using RoyalCode.SmartSearch.Services; namespace RoyalCode.SmartSearch.EntityFramework.Services; @@ -20,14 +18,11 @@ namespace RoyalCode.SmartSearch.EntityFramework.Services; /// /// The type of the entity for which queries are prepared. Must be a reference type. /// -public sealed class CriteriaPerformer : ICriteriaPerformer +public sealed class CriteriaPerformer : CriteriaPerformerBase, ICriteriaPerformer where TDbContext : DbContext where TEntity : class { private readonly TDbContext db; - private readonly ISpecifierFactory specifierFactory; - private readonly IOrderByProvider orderByProvider; - private readonly ISelectorFactory selectorFactory; /// /// Creates a new instance of . @@ -41,37 +36,14 @@ public CriteriaPerformer( ISpecifierFactory specifierFactory, IOrderByProvider orderByProvider, ISelectorFactory selectorFactory) + : base(specifierFactory, orderByProvider, selectorFactory) { this.db = db; - this.specifierFactory = specifierFactory; - this.orderByProvider = orderByProvider; - this.selectorFactory = selectorFactory; } /// - public IPreparedQuery Prepare(CriteriaOptions options) - { - var entities = options.TrackingEnabled + protected override IQueryable GetQueryable(bool trackingEnabled) + => trackingEnabled ? db.Set() : db.Set().AsNoTracking(); - - var criteriaQuery = new CriteriaQuery( - entities, - specifierFactory, - orderByProvider, - selectorFactory); - - foreach (var filter in options.Filters) - filter.ApplyFilter(criteriaQuery); - - criteriaQuery.OrderBy(options.Sortings); - criteriaQuery.SetPageSkipTakeCount( - options.GetPageNumber(), - options.GetSkipCount(), - options.GetTakeCount(), - options.UseCount, - options.LastCount); - - return criteriaQuery; - } } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformerBase.cs b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformerBase.cs new file mode 100644 index 0000000..ab11f53 --- /dev/null +++ b/src/RoyalCode.SmartSearch.EntityFramework/Services/CriteriaPerformerBase.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore; +using RoyalCode.SmartSearch.Defaults; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; +using RoyalCode.SmartSearch.Services; + +namespace RoyalCode.SmartSearch.EntityFramework.Services; + +/// +/// Provides functionality to prepare queries based on specified criteria for a given entity type. +/// +/// +/// This class is designed to facilitate the creation of queries by applying filters, sorting, and +/// selection criteria to an underlying data source. +/// It uses various providers and factories to construct the query dynamically. +/// +/// +/// The type of the entity for which queries are prepared. Must be a reference type. +/// +public abstract class CriteriaPerformerBase : ICriteriaPerformer + where TEntity : class +{ + private readonly ISpecifierFactory specifierFactory; + private readonly IOrderByProvider orderByProvider; + private readonly ISelectorFactory selectorFactory; + + /// + /// Creates a new instance of . + /// + /// The factory to create specifiers for the query. + /// The provider for ordering the results. + /// The factory for selecting results. + protected CriteriaPerformerBase( + ISpecifierFactory specifierFactory, + IOrderByProvider orderByProvider, + ISelectorFactory selectorFactory) + { + this.specifierFactory = specifierFactory; + this.orderByProvider = orderByProvider; + this.selectorFactory = selectorFactory; + } + + /// + public IPreparedQuery Prepare(CriteriaOptions options) + { + var entities = GetQueryable(options.TrackingEnabled); + + var criteriaQuery = new CriteriaQuery( + entities, + specifierFactory, + orderByProvider, + selectorFactory); + + foreach (var filter in options.Filters) + filter.ApplyFilter(criteriaQuery); + + criteriaQuery.OrderBy(options.Sortings); + criteriaQuery.SetPageSkipTakeCount( + options.GetPageNumber(), + options.GetSkipCount(), + options.GetTakeCount(), + options.UseCount, + options.LastCount); + + return criteriaQuery; + } + + /// + /// Provides an for querying entities of type . + /// + /// + /// Use this method to retrieve a queryable source for entities, optionally enabling or disabling + /// change tracking. Disabling tracking is recommended for scenarios where entities are only read and not + /// modified. + /// + /// + /// A value indicating whether change tracking is enabled for the returned query. + ///
+ /// enables change tracking, allowing entity modifications to be tracked by the context. + ///
+ /// disables change tracking, which can improve performance for read-only operations. + /// + /// + /// An that can be used to query entities of type . + /// + protected abstract IQueryable GetQueryable(bool trackingEnabled); +} From 20f9b072362d91fe1dbe5f1722769947954f719e Mon Sep 17 00:00:00 2001 From: eglauko Date: Thu, 10 Jul 2025 02:57:22 -0300 Subject: [PATCH 6/9] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20de=20vers=C3=A3o=20?= =?UTF-8?q?e=20refatora=C3=A7=C3=A3o=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adição de propriedades de versão - Novas propriedades `PropSelVer`, `OpHintVer` e `ProblemsVer` em `Directory.Build.props`. - Atualização de projetos para usar propriedades de versão em vez de números fixos. * Renomeação de métodos - Alteração de `FirstDefaultAsync` para `FirstOrDefaultAsync` em várias interfaces e classes. * Tratamento de erros - Criação da classe de exceção `OrderByException` para erros de ordenação. * Padronização de respostas da API - Introdução de classes `MatchFirst`, `MatchList` e `MatchSearch` para resultados HTTP. - Criação de classes de endpoint para buscas paginadas e filtradas. * Melhoria na documentação da API - Adição de manipulação de metadados para respostas HTTP e geração de OpenAPI. * Atualização da solução - Inclusão do novo projeto `RoyalCode.SmartSearch.AspNetCore` no arquivo da solução. --- src/Directory.Build.props | 5 + .../Exceptions/OrderByException.cs | 22 + .../ICriteria.cs | 2 +- .../ISearch.cs | 2 +- .../Extensions/SearchExtensions.cs | 544 ++++++++++++++++++ .../HttpResults/MatchFirst.cs | 128 +++++ .../HttpResults/MatchList.cs | 132 +++++ .../HttpResults/MatchSearch.cs | 130 +++++ .../HttpResults/ResponseTypeMetadata.cs | 45 ++ .../Internals/FirstEntityEndpoint.cs | 293 ++++++++++ .../Internals/FirstModelEndpoint.cs | 301 ++++++++++ .../Internals/ListEntityEndpoint.cs | 211 +++++++ .../Internals/ListModelEndpoint.cs | 220 +++++++ .../Internals/SearchEntityEndpoint.cs | 226 ++++++++ .../Internals/SearchModelEndpoint.cs | 236 ++++++++ .../RoyalCode.SmartSearch.AspNetCore.csproj | 21 + .../Defaults/Criteria.cs | 2 +- .../Defaults/Search.cs | 2 +- .../RoyalCode.SmartSearch.Core.csproj | 2 +- ...yalCode.SmartSearch.EntityFramework.csproj | 2 +- .../RoyalCode.SmartSearch.Linq.csproj | 2 +- .../AllEntitiesTests.cs | 4 +- src/RoyalCode.SmartSearch.sln | 9 +- 23 files changed, 2531 insertions(+), 10 deletions(-) create mode 100644 src/RoyalCode.SmartSearch.Abstractions/Exceptions/OrderByException.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchFirst.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchList.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchSearch.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/HttpResults/ResponseTypeMetadata.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstEntityEndpoint.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstModelEndpoint.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Internals/ListEntityEndpoint.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Internals/ListModelEndpoint.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchEntityEndpoint.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchModelEndpoint.cs create mode 100644 src/RoyalCode.SmartSearch.AspNetCore/RoyalCode.SmartSearch.AspNetCore.csproj diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 1d2af71..791616e 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -11,4 +11,9 @@ 8.0.2 9.0.5 + + 1.0.2 + 1.0.0 + 1.0.0-preview-4.0 + diff --git a/src/RoyalCode.SmartSearch.Abstractions/Exceptions/OrderByException.cs b/src/RoyalCode.SmartSearch.Abstractions/Exceptions/OrderByException.cs new file mode 100644 index 0000000..3bc22b6 --- /dev/null +++ b/src/RoyalCode.SmartSearch.Abstractions/Exceptions/OrderByException.cs @@ -0,0 +1,22 @@ +namespace RoyalCode.SmartSearch.Exceptions; + +/// +/// Exception thrown when an error occurs related to the sorting of results in a query. +/// +/// Message about the sorting property with problems. +/// The name of the property that caused the sorting error. +/// The type of data to be filtered and sorted. +/// Internal exception that caused the problem. +public sealed class OrderByException(string message, string propertyName, string typeName, Exception inner) + : Exception(message, inner) +{ + /// + /// The name of the property that caused the sorting error. + /// + public string PropertyName { get; } = propertyName; + + /// + /// The type of data to be filtered and sorted. + /// + public string TypeName { get; } = typeName; +} diff --git a/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs b/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs index 343ce4a..ceba2d3 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ICriteria.cs @@ -152,7 +152,7 @@ ISearch Select(Expression> selectExpres /// /// The entity or null if there are no entities that meet the criteria. /// - Task FirstDefaultAsync(CancellationToken cancellationToken = default); + Task FirstOrDefaultAsync(CancellationToken cancellationToken = default); /// /// Apply the filters and sorting and get the first entity that meets the criteria, diff --git a/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs b/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs index 6006faf..b1e78d1 100644 --- a/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs +++ b/src/RoyalCode.SmartSearch.Abstractions/ISearch.cs @@ -148,7 +148,7 @@ public interface ISearch /// /// The entity or null if there are no entities that meet the criteria. /// - Task FirstDefaultAsync(CancellationToken cancellationToken = default); + Task FirstOrDefaultAsync(CancellationToken cancellationToken = default); /// /// Apply the filters and sorting and get the first entity that meets the criteria, diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs b/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs new file mode 100644 index 0000000..195379c --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs @@ -0,0 +1,544 @@ +using Microsoft.AspNetCore.Builder; +using RoyalCode.SmartSearch; +using RoyalCode.SmartSearch.AspNetCore.Internals; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNetCore.Routing; + +public static class SearchExtensions +{ + #region MapSearch + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning entities. + /// + /// The type of entity to be consulted. + /// The type of filter applied. + /// The route builder. + /// The route pattern. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern) + where TEntity : class + where TFilter : class + { + var search = new SearchEntityEndpoint(); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning entities. + /// + /// The type of entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// Custom action to configure the search. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch( + this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var search = new SearchEntityEndpoint(searchAction); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning entities with an additional identifier. + /// + /// The type of entity to be queried. + /// The type of the applied filter. + /// The type of the additional identifier. + /// The route builder. + /// The route pattern. + /// Custom action to configure the search. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch( + this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var search = new SearchEntityEndpoint(searchAction); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning entities with two additional identifiers. + /// + /// The type of entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The route builder. + /// The route pattern. + /// Custom action to configure the search. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch( + this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var search = new SearchEntityEndpoint(searchAction); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning DTOs. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern) + where TEntity : class + where TDto : class + where TFilter : class + { + var search = new SearchModelEndpoint(); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning DTOs. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TDto : class + where TFilter : class + { + var search = new SearchModelEndpoint(searchAction); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning DTOs with an additional identifier. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The type of the additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TDto : class + where TFilter : class + { + var search = new SearchModelEndpoint(searchAction); + return builder.MapGet(pattern, search.Search); + } + + /// + /// Maps a GET endpoint to paginated, sorted and filtered search, returning DTOs with two additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapSearch(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TDto : class + where TFilter : class + { + var search = new SearchModelEndpoint(searchAction); + return builder.MapGet(pattern, search.Search); + } + + #endregion + + #region MapList + + /// + /// Maps a GET endpoint to a filtered and sorted list of entities. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern) + where TEntity : class + where TFilter : class + { + var list = new ListEntityEndpoint(); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of entities. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var list = new ListEntityEndpoint(searchAction); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of entities with an additional identifier. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var list = new ListEntityEndpoint(searchAction); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of entities with two additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var list = new ListEntityEndpoint(searchAction); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of DTOs. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern) + where TEntity : class + where TDto : class + where TFilter : class + { + var list = new ListModelEndpoint(); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of DTOs. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TDto : class + where TFilter : class + { + var list = new ListModelEndpoint(searchAction); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of DTOs with an additional identifier. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The type of the additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TDto : class + where TFilter : class + { + var list = new ListModelEndpoint(searchAction); + return builder.MapGet(pattern, list.List); + } + + /// + /// Maps a GET endpoint to a filtered and sorted list of DTOs with two additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the DTO to be returned. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapList(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TDto : class + where TFilter : class + { + var list = new ListModelEndpoint(searchAction); + return builder.MapGet(pattern, list.List); + } + + #endregion + + #region MapFirst + + /// + /// Maps a GET endpoint to a first entity based on the applied filter. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirst(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first entity based on the applied filter. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirst(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first entity based on the applied filter with an additional identifier. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirst(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first entity based on the applied filter with two additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirst(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first entity based on the applied filter with three additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The type of the third additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirst(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first DTO based on the applied filter. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first DTO based on the applied filter. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first DTO based on the applied filter with an additional identifier. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first DTO based on the applied filter with two additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + /// + /// Maps a GET endpoint to a first DTO based on the applied filter with three additional identifiers. + /// + /// The type of the entity to be queried. + /// The type of the applied filter. + /// The type of the first additional identifier. + /// The type of the second additional identifier. + /// The type of the third additional identifier. + /// The route builder. + /// The route pattern. + /// Action to apply additional logic to the search criteria. + /// A builder for additional endpoint configuration. + public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + [StringSyntax("Route")] string pattern, + Action> searchAction) + where TEntity : class + where TFilter : class + { + var first = new FirstEntityEndpoint(searchAction); + return builder.MapGet(pattern, first.First); + } + + #endregion +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchFirst.cs b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchFirst.cs new file mode 100644 index 0000000..ab5bc28 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchFirst.cs @@ -0,0 +1,128 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http.Metadata; +using RoyalCode.SmartProblems; +using RoyalCode.SmartProblems.HttpResults; +using System.Net.Mime; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace RoyalCode.SmartSearch.AspNetCore.HttpResults; + +public class MatchFirst : IResult, INestedHttpResult, IEndpointMetadataProvider + where TModel : class +{ + /// + /// Conversão implícita de para . + /// + /// Resultado NoContent. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchFirst(NoContent result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Resultado Ok com lista de modelos. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchFirst(Ok result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Resultado Ok com lista de modelos. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchFirst(TModel result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Problema ocorrido na operação. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchFirst(Problem problem) => new(problem); + + /// + /// Conversão implícita de para . + /// + /// Exceção lançada na operação. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchFirst(Exception ex) => new(ex); + + /// + /// Cria um resultado de lista sem conteúdo (204). + /// + public MatchFirst() + { + Result = TypedResults.NoContent(); + } + + /// + /// Cria um resultado de lista a partir de uma lista de modelos (200). + /// + /// Modelo retornado. + public MatchFirst(TModel model) + { + Result = TypedResults.Ok(model); + } + + /// + /// Cria um resultado de lista a partir de uma exceção (500). + /// + /// Exceção lançada. + public MatchFirst(Exception ex) + { + MatchErrorResult match = ex; + Result = match; + } + + /// + /// Cria um resultado de lista a partir de um problema (400/500). + /// + /// Problema ocorrido. + public MatchFirst(Problem problem) + { + MatchErrorResult match = problem; + Result = match; + } + + /// + /// Cria um resultado de lista a partir de um resultado Ok com lista de modelos (200). + /// + /// Resultado Ok com lista de modelos. + public MatchFirst(Ok result) + { + Result = result; + } + + /// + /// Cria um resultado de lista a partir de um resultado NoContent (204). + /// + /// Resultado NoContent. + public MatchFirst(NoContent result) + { + Result = result; + } + + /// + /// Resultado HTTP encapsulado (Ok, NoContent, Problem, etc). + /// + public IResult Result { get; } + + /// + /// Adiciona metadados de resposta para documentação e OpenAPI. + /// + /// Método associado ao endpoint. + /// Construtor do endpoint. + public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + OkMatch.PopulateMetadata(method, builder); + builder.Metadata.Add(new ResponseTypeMetadata(StatusCodes.Status204NoContent, MediaTypeNames.Application.Json)); + } + + /// + /// Executa o resultado HTTP no contexto da requisição. + /// + /// Contexto HTTP. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task ExecuteAsync(HttpContext httpContext) => Result.ExecuteAsync(httpContext); +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchList.cs b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchList.cs new file mode 100644 index 0000000..a363fa6 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchList.cs @@ -0,0 +1,132 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http.Metadata; +using RoyalCode.SmartProblems; +using RoyalCode.SmartProblems.HttpResults; +using System.Net.Mime; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace RoyalCode.SmartSearch.AspNetCore.HttpResults; + +/// +/// +/// Representa um resultado HTTP para uma listagem de entidades ou DTOs em operações de busca. +/// +/// +/// Permite retornar resultados padronizados (200, 204, 400, 500) em Minimal APIs e Controllers, +/// encapsulando listas, erros e exceções de forma consistente. +/// +/// +/// Tipo do modelo dos itens retornados na lista. +public class MatchList : IResult, INestedHttpResult, IEndpointMetadataProvider + where TModel : class +{ + /// + /// Conversão implícita de para . + /// + /// Resultado NoContent. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchList(NoContent result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Resultado Ok com lista de modelos. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchList(Ok> result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Problema ocorrido na operação. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchList(Problem problem) => new(problem); + + /// + /// Conversão implícita de para . + /// + /// Exceção lançada na operação. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchList(Exception ex) => new(ex); + + /// + /// Cria um resultado de lista sem conteúdo (204). + /// + public MatchList() + { + Result = TypedResults.NoContent(); + } + + /// + /// Cria um resultado de lista a partir de uma lista de modelos (200). + /// + /// Lista de modelos retornados. + public MatchList(IReadOnlyList result) + { + Result = TypedResults.Ok(result); + } + + /// + /// Cria um resultado de lista a partir de uma exceção (500). + /// + /// Exceção lançada. + public MatchList(Exception ex) + { + MatchErrorResult match = ex; + Result = match; + } + + /// + /// Cria um resultado de lista a partir de um problema (400/500). + /// + /// Problema ocorrido. + public MatchList(Problem problem) + { + MatchErrorResult match = problem; + Result = match; + } + + /// + /// Cria um resultado de lista a partir de um resultado Ok com lista de modelos (200). + /// + /// Resultado Ok com lista de modelos. + public MatchList(Ok> result) + { + Result = result; + } + + /// + /// Cria um resultado de lista a partir de um resultado NoContent (204). + /// + /// Resultado NoContent. + public MatchList(NoContent result) + { + Result = result; + } + + /// + /// Resultado HTTP encapsulado (Ok, NoContent, Problem, etc). + /// + public IResult Result { get; } + + /// + /// Adiciona metadados de resposta para documentação e OpenAPI. + /// + /// Método associado ao endpoint. + /// Construtor do endpoint. + public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add(new ResponseTypeMetadata(typeof(IReadOnlyList), StatusCodes.Status200OK, MediaTypeNames.Application.Json)); + builder.Metadata.Add(new ResponseTypeMetadata(StatusCodes.Status204NoContent, MediaTypeNames.Application.Json)); + MatchErrorResult.PopulateMetadata(method, builder); + } + + /// + /// Executa o resultado HTTP no contexto da requisição. + /// + /// Contexto HTTP. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task ExecuteAsync(HttpContext httpContext) => Result.ExecuteAsync(httpContext); +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchSearch.cs b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchSearch.cs new file mode 100644 index 0000000..0781a89 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/MatchSearch.cs @@ -0,0 +1,130 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Http.Metadata; +using RoyalCode.SmartProblems; +using RoyalCode.SmartProblems.HttpResults; +using System.Net.Mime; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace RoyalCode.SmartSearch.AspNetCore.HttpResults; + +/// +/// Representa um resultado HTTP para uma pesquisa paginada de entidades ou DTOs, incluindo informações de paginação e ordenação. +/// +/// Permite retornar resultados padronizados (200, 204, 400, 500) em Minimal APIs e Controllers, +/// encapsulando listas paginadas, erros e exceções de forma consistente. +/// +/// +/// Tipo do modelo dos itens retornados na pesquisa. +public class MatchSearch : IResult, INestedHttpResult, IEndpointMetadataProvider + where TModel : class +{ + /// + /// Conversão implícita de para . + /// + /// Resultado NoContent. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchSearch(NoContent result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Resultado Ok com lista paginada de modelos. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchSearch(Ok> result) => new(result); + + /// + /// Conversão implícita de para . + /// + /// Problema ocorrido na operação. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchSearch(Problem problem) => new(problem); + + /// + /// Conversão implícita de para . + /// + /// Exceção lançada na operação. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator MatchSearch(Exception ex) => new(ex); + + /// + /// Cria um resultado de pesquisa sem conteúdo (204). + /// + public MatchSearch() + { + Result = TypedResults.NoContent(); + } + + /// + /// Cria um resultado de pesquisa a partir de uma lista paginada de modelos (200). + /// + /// Lista paginada de modelos retornados. + public MatchSearch(IResultList result) + { + Result = TypedResults.Ok(result); + } + + /// + /// Cria um resultado de pesquisa a partir de uma exceção (500). + /// + /// Exceção lançada. + public MatchSearch(Exception ex) + { + MatchErrorResult match = ex; + Result = match; + } + + /// + /// Cria um resultado de pesquisa a partir de um problema (400/500). + /// + /// Problema ocorrido. + public MatchSearch(Problem problem) + { + MatchErrorResult match = problem; + Result = match; + } + + /// + /// Cria um resultado de pesquisa a partir de um resultado Ok com lista paginada de modelos (200). + /// + /// Resultado Ok com lista paginada de modelos. + public MatchSearch(Ok> result) + { + Result = result; + } + + /// + /// Cria um resultado de pesquisa a partir de um resultado NoContent (204). + /// + /// Resultado NoContent. + public MatchSearch(NoContent result) + { + Result = result; + } + + /// + /// Resultado HTTP encapsulado (Ok, NoContent, Problem, etc). + /// + public IResult Result { get; } + + /// + /// Adiciona metadados de resposta para documentação e OpenAPI. + /// + /// Método associado ao endpoint. + /// Construtor do endpoint. + public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) + { + builder.Metadata.Add(new ResponseTypeMetadata(typeof(IResultList), StatusCodes.Status200OK, MediaTypeNames.Application.Json)); + builder.Metadata.Add(new ResponseTypeMetadata(StatusCodes.Status204NoContent, MediaTypeNames.Application.Json)); + MatchErrorResult.PopulateMetadata(method, builder); + } + + /// + /// Executa o resultado HTTP no contexto da requisição. + /// + /// Contexto HTTP. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task ExecuteAsync(HttpContext httpContext) => Result.ExecuteAsync(httpContext); +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/ResponseTypeMetadata.cs b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/ResponseTypeMetadata.cs new file mode 100644 index 0000000..0d612ac --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/HttpResults/ResponseTypeMetadata.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Http.Metadata; +using System.Net.Mime; + +namespace RoyalCode.SmartSearch.AspNetCore.HttpResults; + +/// +/// Metadados de resposta para endpoints HTTP, utilizados para documentação e geração de OpenAPI/Swagger. +/// +internal sealed class ResponseTypeMetadata : IProducesResponseTypeMetadata +{ + /// + /// Inicializa uma nova instância de com tipo de retorno, status e tipos de conteúdo. + /// + /// Tipo do objeto retornado. + /// Código de status HTTP. + /// Tipos de conteúdo suportados (ex: application/json). + public ResponseTypeMetadata(Type? type, int statusCode, params string[]? contentTypes) + { + Type = type; + StatusCode = statusCode; + ContentTypes = contentTypes ?? [MediaTypeNames.Application.Json]; + } + /// + /// Inicializa uma nova instância de apenas com status e tipos de conteúdo. + /// + /// Código de status HTTP. + /// Tipos de conteúdo suportados (ex: application/json). + public ResponseTypeMetadata(int statusCode, params string[]? contentTypes) + { + StatusCode = statusCode; + ContentTypes = contentTypes ?? [MediaTypeNames.Application.Json]; + } + /// + /// Tipo do objeto retornado na resposta HTTP. + /// + public Type? Type { get; } + /// + /// Código de status HTTP da resposta. + /// + public int StatusCode { get; } + /// + /// Tipos de conteúdo suportados pela resposta. + /// + public IEnumerable ContentTypes { get; } +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstEntityEndpoint.cs b/src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstEntityEndpoint.cs new file mode 100644 index 0000000..9e78ff1 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstEntityEndpoint.cs @@ -0,0 +1,293 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RoyalCode.SmartProblems; +using RoyalCode.SmartSearch.AspNetCore.HttpResults; +using RoyalCode.SmartSearch.Exceptions; + +namespace RoyalCode.SmartSearch.AspNetCore.Internals; + +/// +/// Endpoint to get the first item in the entity that meets the filter and sort criteria. +/// +/// The type of entity to be queried. +/// The type of filter applied. +public class FirstEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search. + public FirstEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity that meets the filter and sort criteria. + /// + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(search); + + var entity = await search.FirstOrDefaultAsync(ct); + + if (entity is null) + { + return TypedResults.NoContent(); + } + + return entity; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} + +/// +/// Endpoint to get the first entity item with additional identifier. +/// +/// The type of entity to be queried. +/// The type of the applied filter. +/// The type of the additional identifier. +public class FirstEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifier. + public FirstEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity with additional identifier. + /// + /// Additional identifier for the search. + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [FromRoute] TId id, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(id, search); + + var entity = await search.FirstOrDefaultAsync(ct); + + if (entity is null) + { + return TypedResults.NoContent(); + } + + return entity; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} + +/// +/// Endpoint to get the first entity item with two additional identifiers. +/// +/// The type of entity to be queried. +/// The type of the applied filter. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +public class FirstEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifiers. + public FirstEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity with two additional identifiers. + /// + /// Additional primary identifier for the search. + /// Additional identifier, related to the first, for the search. + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(id, relatedId, search); + + var entity = await search.FirstOrDefaultAsync(ct); + + if (entity is null) + { + return TypedResults.NoContent(); + } + + return entity; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} + +/// +/// Endpoint to get the first entity item with three additional identifiers. +/// +/// The type of entity to be queried. +/// The type of the applied filter. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +/// The type of the third additional identifier. +public class FirstEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifiers. + public FirstEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity with three additional identifiers. + /// + /// Additional primary identifier for the search. + /// Additional identifier, related to the first, for the search. + /// Third additional identifier for the search. + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [FromRoute] TId3 subRelatedId, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(id, relatedId, subRelatedId, search); + + var entity = await search.FirstOrDefaultAsync(ct); + + if (entity is null) + { + return TypedResults.NoContent(); + } + + return entity; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstModelEndpoint.cs b/src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstModelEndpoint.cs new file mode 100644 index 0000000..06bd6cf --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Internals/FirstModelEndpoint.cs @@ -0,0 +1,301 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RoyalCode.SmartProblems; +using RoyalCode.SmartSearch.AspNetCore.HttpResults; +using RoyalCode.SmartSearch.Exceptions; + +namespace RoyalCode.SmartSearch.AspNetCore.Internals; + +/// +/// Endpoint to get the first item in the entity that meets the filter and sort criteria. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of filter applied. +public class FirstModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search. + public FirstModelEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity that meets the filter and sort criteria. + /// + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(search); + + var dto = await search.Select().FirstOrDefaultAsync(ct); + + if (dto is null) + { + return TypedResults.NoContent(); + } + + return dto; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} + +/// +/// Endpoint to get the first entity item with additional identifier. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of the applied filter. +/// The type of the additional identifier. +public class FirstModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifier. + public FirstModelEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity with additional identifier. + /// + /// Additional identifier for the search. + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [FromRoute] TId id, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(id, search); + + var dto = await search.Select().FirstOrDefaultAsync(ct); + + if (dto is null) + { + return TypedResults.NoContent(); + } + + return dto; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} + +/// +/// Endpoint to get the first entity item with two additional identifiers. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of the applied filter. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +public class FirstModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifiers. + public FirstModelEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity with two additional identifiers. + /// + /// Additional primary identifier for the search. + /// Additional identifier, related to the first, for the search. + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(id, relatedId, search); + + var dto = await search.Select().FirstOrDefaultAsync(ct); + + if (dto is null) + { + return TypedResults.NoContent(); + } + + return dto; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} + +/// +/// Endpoint to get the first entity item with three additional identifiers. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of the applied filter. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +/// The type of the third additional identifier. +public class FirstModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifiers. + public FirstModelEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the search for the first item in the entity with three additional identifiers. + /// + /// Additional primary identifier for the search. + /// Additional identifier, related to the first, for the search. + /// Third additional identifier for the search. + /// Filter object with search criteria. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with the item, 204 if not found, or 400 in case of sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> First( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [FromRoute] TId3 subRelatedId, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(id, relatedId, subRelatedId, search); + + var dto = await search.Select().FirstOrDefaultAsync(ct); + + if (dto is null) + { + return TypedResults.NoContent(); + } + + return dto; + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return Problems.InternalError(ex); + } + } +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Internals/ListEntityEndpoint.cs b/src/RoyalCode.SmartSearch.AspNetCore/Internals/ListEntityEndpoint.cs new file mode 100644 index 0000000..a22bc08 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Internals/ListEntityEndpoint.cs @@ -0,0 +1,211 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RoyalCode.SmartProblems; +using RoyalCode.SmartSearch.AspNetCore.HttpResults; +using RoyalCode.SmartSearch.Exceptions; + +namespace RoyalCode.SmartSearch.AspNetCore.Internals; + +/// +/// Endpoint for filtered and sorted listing of Entities. +/// +/// The type of entity to be queried. +/// The type of filter applied. +public class ListEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? listAction; + + /// + /// Initializes a new instance of . + /// + /// Action to customize the listing criteria. + public ListEntityEndpoint(Action>? listAction = null) + { + this.listAction = listAction; + } + + /// + /// Performs a filtered and sorted list of Entities. + /// + /// A filter object with search criteria. + /// Ordering criteria. + /// A search service for the entity. + /// The cancellation token. + /// HTTP 200 result with list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> List( + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (listAction is not null) + listAction(search); + + var result = await search.AsSearch().ToListAsync(ct); + + if (result.Count == 0) + return TypedResults.NoContent(); + + return TypedResults.Ok(result.Items); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// Endpoint for filtered and sorted listing of entities with additional identifier. +/// +/// The type of entity to be queried. +/// The type of filter applied. +/// The type of the additional identifier. +public class ListEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? listAction; + + /// + /// Initializes a new instance of . + /// + /// Action to customize the listing criteria with an additional identifier. + public ListEntityEndpoint(Action>? listAction = null) + { + this.listAction = listAction; + } + + /// + /// Performs a filtered and sorted list of entities with an additional identifier. + /// + /// The additional identifier for the list. + /// A filter object with search criteria. + /// Ordering criteria. + /// A search service for the entity. + /// The cancellation token. + /// HTTP 200 result with list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> List( + [FromRoute] TId id, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (listAction is not null) + listAction(id, search); + + var result = await search.AsSearch().ToListAsync(ct); + + if (result.Count == 0) + return TypedResults.NoContent(); + + return TypedResults.Ok(result.Items); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// Endpoint for filtered and sorted listing of entities with two additional identifiers. +/// +/// The type of entity to be queried. +/// The type of filter applied. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +public class ListEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? listAction; + + /// + /// Initializes a new instance of . + /// + /// Action to customize the listing criteria with two additional identifiers. + public ListEntityEndpoint(Action>? listAction = null) + { + this.listAction = listAction; + } + + /// + /// Performs a filtered and sorted list of entities with two additional identifiers. + /// + /// The first additional identifier for the list. + /// The second additional identifier for the list. + /// A filter object with search criteria. + /// Ordering criteria. + /// A search service for the entity. + /// The cancellation token. + /// HTTP 200 result with list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> List( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (listAction is not null) + listAction(id, relatedId, search); + + var result = await search.AsSearch().ToListAsync(ct); + + if (result.Count == 0) + return TypedResults.NoContent(); + + return TypedResults.Ok(result.Items); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Internals/ListModelEndpoint.cs b/src/RoyalCode.SmartSearch.AspNetCore/Internals/ListModelEndpoint.cs new file mode 100644 index 0000000..5ec43d0 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Internals/ListModelEndpoint.cs @@ -0,0 +1,220 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RoyalCode.SmartProblems; +using RoyalCode.SmartSearch.AspNetCore.HttpResults; +using RoyalCode.SmartSearch.Exceptions; + +namespace RoyalCode.SmartSearch.AspNetCore.Internals; + +/// +/// Endpoint for filtered and sorted listing of DTOs. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of filter applied. +public class ListModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? listAction; + + /// + /// Initializes a new instance of . + /// + /// Action to customize the listing criteria. + public ListModelEndpoint(Action>? listAction = null) + { + this.listAction = listAction; + } + + /// + /// Performs a filtered and sorted list of DTOs. + /// + /// A filter object with search criteria. + /// Ordering criteria. + /// A search service for the entity. + /// The cancellation token. + /// HTTP 200 result with list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> List( + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (listAction is not null) + listAction(search); + + var select = search.Select(); + var result = await select.ToListAsync(ct); + + if (result.Count == 0) + return TypedResults.NoContent(); + + return TypedResults.Ok(result.Items); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// Endpoint for filtered and sorted listing of DTOs with additional identifier. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of filter applied. +/// The type of the additional identifier. +public class ListModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? listAction; + + /// + /// Initializes a new instance of . + /// + /// Action to customize the listing criteria with an additional identifier. + public ListModelEndpoint(Action>? listAction = null) + { + this.listAction = listAction; + } + + /// + /// Performs a filtered and sorted list of DTOs with an additional identifier. + /// + /// The additional identifier for the list. + /// A filter object with search criteria. + /// Ordering criteria. + /// A search service for the entity. + /// The cancellation token. + /// HTTP 200 result with list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> List( + [FromRoute] TId id, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (listAction is not null) + listAction(id, search); + + var select = search.Select(); + var result = await select.ToListAsync(ct); + + if (result.Count == 0) + return TypedResults.NoContent(); + + return TypedResults.Ok(result.Items); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// Endpoint for filtered and sorted listing of DTOs with two additional identifiers. +/// +/// The type of entity to be queried. +/// The type of DTO to be returned. +/// The type of filter applied. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +public class ListModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? listAction; + + /// + /// Initializes a new instance of . + /// + /// Action to customize the listing criteria with two additional identifiers. + public ListModelEndpoint(Action>? listAction = null) + { + this.listAction = listAction; + } + + /// + /// Performs a filtered and sorted list of DTOs with two additional identifiers. + /// + /// The first additional identifier for the list. + /// The second additional identifier for the list. + /// A filter object with search criteria. + /// Ordering criteria. + /// A search service for the entity. + /// The cancellation token. + /// HTTP 200 result with list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> List( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [AsParameters] TFilter filtro, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .OrderBy(orderby) + .FilterBy(filtro); + + if (listAction is not null) + listAction(id, relatedId, search); + + var select = search.Select(); + var result = await select.ToListAsync(ct); + + if (result.Count == 0) + return TypedResults.NoContent(); + + return TypedResults.Ok(result.Items); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchEntityEndpoint.cs b/src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchEntityEndpoint.cs new file mode 100644 index 0000000..3a43877 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchEntityEndpoint.cs @@ -0,0 +1,226 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RoyalCode.SmartProblems; +using RoyalCode.SmartSearch.AspNetCore.HttpResults; +using RoyalCode.SmartSearch.Exceptions; + +namespace RoyalCode.SmartSearch.AspNetCore.Internals; + +/// +/// It allows you to customize the paged, sorted and filtered search for entities, applying additional logic via delegate. +/// +/// The type of the entity to be queried. +/// The type of the applied filter. +public class SearchEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search. + public SearchEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the custom search, applying filters, sorting, pagination, and additional logic, returning paginated entities. + /// + /// Filter object with search criteria. + /// Pagination and counting parameters. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with paginated list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> Search( + [AsParameters] TFilter filtro, + [AsParameters] SearchOptions options, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .WithOptions(options) + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction != null) + searchAction(search); + + var result = await search.AsSearch().ToListAsync(ct); + + if (result.Count == 0) + { + return TypedResults.NoContent(); + } + + return TypedResults.Ok(result); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// It allows you to customize the paginated, sorted and filtered search for entities, using an additional identifier and custom logic. +/// +/// The type of the entity to be queried. +/// The type of the applied filter. +/// The type of the additional identifier. +public class SearchEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifier. + public SearchEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the custom search, applying filters, sorting, pagination, and additional logic, returning paginated entities. + /// + /// Additional identifier for the search. + /// Filter object with search criteria. + /// Pagination and counting parameters. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with paginated list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> Search( + TId id, + [AsParameters] TFilter filtro, + [AsParameters] SearchOptions options, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .WithOptions(options) + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction != null) + searchAction(id, search); + + var result = await search.AsSearch().ToListAsync(ct); + + if (result.Count == 0) + { + return TypedResults.NoContent(); + } + + return TypedResults.Ok(result); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// It allows you to customize the paginated, sorted and filtered search for entities, using two additional identifiers and custom logic. +/// +/// The type of the entity to be queried. +/// The type of the applied filter. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +public class SearchEntityEndpoint + where TEntity : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifiers. + public SearchEntityEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Executes the custom search, applying filters, sorting, pagination, and additional logic, returning paginated entities. + /// + /// The first additional identifier for the search. + /// The second additional identifier for the search. + /// Filter object with search criteria. + /// Pagination and counting parameters. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP 200 result with paginated list, 204 if empty, or 400 on sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> Search( + TId1 id, + TId2 relatedId, + [AsParameters] TFilter filtro, + [AsParameters] SearchOptions options, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .WithOptions(options) + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction != null) + searchAction(id, relatedId, search); + + var result = await search.AsSearch().ToListAsync(ct); + + if (result.Count == 0) + { + return TypedResults.NoContent(); + } + + return TypedResults.Ok(result); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchModelEndpoint.cs b/src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchModelEndpoint.cs new file mode 100644 index 0000000..ae217cd --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/Internals/SearchModelEndpoint.cs @@ -0,0 +1,236 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RoyalCode.SmartProblems; +using RoyalCode.SmartSearch.AspNetCore.HttpResults; +using RoyalCode.SmartSearch.Exceptions; + +namespace RoyalCode.SmartSearch.AspNetCore.Internals; + +/// +/// It allows you to customize the paginated, sorted and filtered search of entities, with projection to DTO, applying additional logic via delegate. +/// +/// The type of the entity to be queried. +/// The type of the DTO to be returned. +/// The type of the applied filter. +public class SearchModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action>? searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search. + public SearchModelEndpoint(Action>? searchAction = null) + { + this.searchAction = searchAction; + } + + /// + /// Performs customized searches, applying filters, sorting, pagination and additional logic, returning paginated DTOs. + /// + /// Filter object with search criteria. + /// Paging and counting parameters. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP result 200 with paginated list, 204 if empty, or 400 in sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> Search( + [AsParameters] TFilter filtro, + [AsParameters] SearchOptions options, + [FromQuery] Sorting[]? orderby, + ICriteria criteria, + CancellationToken ct) + { + try + { + var search = criteria + .WithOptions(options) + .OrderBy(orderby) + .FilterBy(filtro); + + if (searchAction is not null) + searchAction(search); + + var select = search.Select(); + + var result = await select.ToListAsync(ct); + + if (result.Count == 0) + { + return TypedResults.NoContent(); + } + + return TypedResults.Ok(result); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// It allows you to customize the paginated, sorted and filtered search for entities, with projection to DTO, using an additional identifier and custom logic. +/// +/// The type of the entity to be queried. +/// The type of the DTO to be returned. +/// The type of the applied filter. +/// The type of the additional identifier. +public class SearchModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action> searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifier. + public SearchModelEndpoint(Action> searchAction) + { + this.searchAction = searchAction; + } + + /// + /// Executes the customized search, applying filters, sorting, pagination and additional logic, returning paginated DTOs. + /// + /// The additional identifier for the search. + /// Filter object with search criteria. + /// Paging and counting parameters. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP result 200 with paginated list, 204 if empty, or 400 in sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> Search( + [FromRoute] TId id, + [AsParameters] TFilter filtro, + [AsParameters] SearchOptions options, + [FromQuery] Sorting[]? orderby, + ICriteria criteria, + CancellationToken ct) + { + try + { + var search = criteria + .WithOptions(options) + .OrderBy(orderby) + .FilterBy(filtro); + + searchAction(id, search); + + var select = search.Select(); + + var result = await select.ToListAsync(ct); + + if (result.Count == 0) + { + return TypedResults.NoContent(); + } + + return TypedResults.Ok(result); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} + +/// +/// It allows you to customize the paginated, sorted and filtered search for entities, with projection to DTO, using an additional identifier and custom logic. +/// +/// The type of the entity to be queried. +/// The type of the DTO to be returned. +/// The type of the applied filter. +/// The type of the first additional identifier. +/// The type of the second additional identifier. +public class SearchModelEndpoint + where TEntity : class + where TDto : class + where TFilter : class +{ + private readonly Action> searchAction; + + /// + /// Initializes a new instance of . + /// + /// Custom action to configure the search with identifiers. + public SearchModelEndpoint(Action> searchAction) + { + this.searchAction = searchAction; + } + + /// + /// Executes the customized search, applying filters, sorting, pagination and additional logic, returning paginated DTOs. + /// + /// The primary additional identifier for the search. + /// The additional identifier, related to the first, for the search. + /// Filter object with search criteria. + /// Paging and counting parameters. + /// Sorting criteria. + /// Search service for the entity. + /// Cancellation token. + /// HTTP result 200 with paginated list, 204 if empty, or 400 in sorting error. + [ProduceProblems(ProblemCategory.InvalidParameter, ProblemCategory.InternalServerError)] + public async Task> Search( + [FromRoute] TId1 id, + [FromRoute] TId2 relatedId, + [AsParameters] TFilter filtro, + [AsParameters] SearchOptions options, + [FromQuery] Sorting[]? orderby, + ICriteria searchable, + CancellationToken ct) + { + try + { + var search = searchable + .WithOptions(options) + .OrderBy(orderby) + .FilterBy(filtro); + + searchAction(id, relatedId, search); + + var select = search.Select(); + + var result = await select.ToListAsync(ct); + + if (result.Count == 0) + { + return TypedResults.NoContent(); + } + + return TypedResults.Ok(result); + } + catch (OrderByException obex) + { + return Problems.InvalidParameter(obex.Message, nameof(orderby)) + .With("propertyName", obex.PropertyName) + .With("typeName", obex.TypeName) + .With("orderby", orderby); + } + catch (Exception ex) + { + return ex; + } + } +} \ No newline at end of file diff --git a/src/RoyalCode.SmartSearch.AspNetCore/RoyalCode.SmartSearch.AspNetCore.csproj b/src/RoyalCode.SmartSearch.AspNetCore/RoyalCode.SmartSearch.AspNetCore.csproj new file mode 100644 index 0000000..591df40 --- /dev/null +++ b/src/RoyalCode.SmartSearch.AspNetCore/RoyalCode.SmartSearch.AspNetCore.csproj @@ -0,0 +1,21 @@ + + + + + + $(LibTargets) + + + + + + + + + + + + + + + diff --git a/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs b/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs index 9834eef..c26e53c 100644 --- a/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs +++ b/src/RoyalCode.SmartSearch.Core/Defaults/Criteria.cs @@ -143,7 +143,7 @@ public async Task ExistsAsync(CancellationToken cancellationToken = defaul public TEntity? FirstOrDefault() => performer.Prepare(options).FirstOrDefault(); /// - public async Task FirstDefaultAsync(CancellationToken cancellationToken = default) + public async Task FirstOrDefaultAsync(CancellationToken cancellationToken = default) => await performer.Prepare(options).FirstOrDefaultAsync(cancellationToken); /// diff --git a/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs b/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs index 1388de2..8078f12 100644 --- a/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs +++ b/src/RoyalCode.SmartSearch.Core/Defaults/Search.cs @@ -103,7 +103,7 @@ public Task> ToAsyncListAsync(CancellationToken token) public TDto? FirstOrDefault() => performer.Prepare(options).Select(searchSelect).FirstOrDefault(); /// - public Task FirstDefaultAsync(CancellationToken cancellationToken = default) + public Task FirstOrDefaultAsync(CancellationToken cancellationToken = default) => performer.Prepare(options).Select(searchSelect).FirstOrDefaultAsync(cancellationToken); /// diff --git a/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj b/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj index 7aa73bc..3476e59 100644 --- a/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj +++ b/src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/RoyalCode.SmartSearch.EntityFramework/RoyalCode.SmartSearch.EntityFramework.csproj b/src/RoyalCode.SmartSearch.EntityFramework/RoyalCode.SmartSearch.EntityFramework.csproj index 208e70e..1c7e9c6 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/RoyalCode.SmartSearch.EntityFramework.csproj +++ b/src/RoyalCode.SmartSearch.EntityFramework/RoyalCode.SmartSearch.EntityFramework.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/RoyalCode.SmartSearch.Linq/RoyalCode.SmartSearch.Linq.csproj b/src/RoyalCode.SmartSearch.Linq/RoyalCode.SmartSearch.Linq.csproj index c47aed2..8422814 100644 --- a/src/RoyalCode.SmartSearch.Linq/RoyalCode.SmartSearch.Linq.csproj +++ b/src/RoyalCode.SmartSearch.Linq/RoyalCode.SmartSearch.Linq.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs b/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs index c98f984..3569b51 100644 --- a/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs @@ -258,7 +258,7 @@ public async Task Must_First_WhenFilterByNameAsync() // act var filter = new SimpleFilter { Name = "B" }; - SimpleModel? result = await all.FilterBy(filter).FirstDefaultAsync(); + SimpleModel? result = await all.FilterBy(filter).FirstOrDefaultAsync(); // assert result.Should().NotBeNull().And.Match(x => x.Id == 2); @@ -306,7 +306,7 @@ public async Task Must_FirstBeNull_WhenFilterByNameAsync() // act var filter = new SimpleFilter { Name = "D" }; - SimpleModel? result = await all.FilterBy(filter).FirstDefaultAsync(); + SimpleModel? result = await all.FilterBy(filter).FirstOrDefaultAsync(); // assert result.Should().BeNull(); diff --git a/src/RoyalCode.SmartSearch.sln b/src/RoyalCode.SmartSearch.sln index 7db142c..3fe5965 100644 --- a/src/RoyalCode.SmartSearch.sln +++ b/src/RoyalCode.SmartSearch.sln @@ -10,8 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".items", ".items", "{45312F base.targets = base.targets Directory.Build.props = Directory.Build.props icon.png = icon.png - tests.targets = tests.targets ..\.github\workflows\smart-search.yml = ..\.github\workflows\smart-search.yml + tests.targets = tests.targets EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoyalCode.SmartSearch.Linq", "RoyalCode.SmartSearch.Linq\RoyalCode.SmartSearch.Linq.csproj", "{DBEBDD2C-37A5-4D17-9DB9-1ACD67749E13}" @@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{932A3AFB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoyalCode.SmartSearch.Tests", "RoyalCode.SmartSearch.Tests\RoyalCode.SmartSearch.Tests.csproj", "{92A0A603-CFF6-498E-BED8-8D0D21561AC2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoyalCode.SmartSearch.AspNetCore", "RoyalCode.SmartSearch.AspNetCore\RoyalCode.SmartSearch.AspNetCore.csproj", "{BC54B3A6-736A-4459-9F33-1101013627F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,6 +54,10 @@ Global {92A0A603-CFF6-498E-BED8-8D0D21561AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {92A0A603-CFF6-498E-BED8-8D0D21561AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU {92A0A603-CFF6-498E-BED8-8D0D21561AC2}.Release|Any CPU.Build.0 = Release|Any CPU + {BC54B3A6-736A-4459-9F33-1101013627F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC54B3A6-736A-4459-9F33-1101013627F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC54B3A6-736A-4459-9F33-1101013627F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC54B3A6-736A-4459-9F33-1101013627F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -62,6 +68,7 @@ Global {DDAB919D-3519-4208-A2B1-DCE122228400} = {EB20012D-6B82-492A-A7C8-9B45D10C0364} {DEAF76E8-950F-4D38-B98B-9099C9F190ED} = {EB20012D-6B82-492A-A7C8-9B45D10C0364} {92A0A603-CFF6-498E-BED8-8D0D21561AC2} = {932A3AFB-78D0-4A2D-BADD-54A9BC593B60} + {BC54B3A6-736A-4459-9F33-1101013627F6} = {EB20012D-6B82-492A-A7C8-9B45D10C0364} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D75591B5-4D8A-4ECD-94CE-D38BBC572272} From f58f934d2175566fc6627758225e757a8b3cbb3b Mon Sep 17 00:00:00 2001 From: eglauko Date: Thu, 17 Jul 2025 22:39:17 -0300 Subject: [PATCH 7/9] =?UTF-8?q?Atualiza=20depend=C3=AAncias=20e=20refatora?= =?UTF-8?q?=20m=C3=A9todos=20de=20busca?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mudanças nas configurações e dependências do projeto. * Atualização do arquivo `Directory.Build.props` - Alterado `SearchesPreview` e `ProblemsVer` para novas versões. - Atualizados `LibTargets` e `AspTargets` para incluir `net8` e `net9`. * Refatoração dos métodos em `SearchExtensions.cs` - Renomeados métodos de `MapFirstDto` para `MapSelectFirst`. - Atualizada lógica interna para usar `FirstModelEndpoint`. Mudanças nas dependências de teste. * Atualização do arquivo `tests.targets` - Versões de `Microsoft.NET.Test.Sdk`, `xunit.runner.visualstudio` e `FluentAssertions` atualizadas. Adição de nova funcionalidade para critérios de busca. * Inclusão do arquivo `EFSearchesExtensions.cs` - Adicionada classe `EFSearchesExtensions` com método `Criteria` para `DbContext`. --- src/Directory.Build.props | 4 +- .../Extensions/SearchExtensions.cs | 30 +++++++++----- .../Extensions/EFSearchesExtensions.cs | 41 +++++++++++++++++++ ...workSearchesServiceCollectionExtensions.cs | 31 -------------- src/tests.targets | 6 +-- 5 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 src/RoyalCode.SmartSearch.EntityFramework/Extensions/EFSearchesExtensions.cs diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 791616e..12de973 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 0.8.0 - -preview-2 + -preview-3 8.0.2 @@ -14,6 +14,6 @@ 1.0.2 1.0.0 - 1.0.0-preview-4.0 + 1.0.0-preview-4.3 diff --git a/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs b/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs index 195379c..db55c65 100644 --- a/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs +++ b/src/RoyalCode.SmartSearch.AspNetCore/Extensions/SearchExtensions.cs @@ -445,16 +445,18 @@ public static RouteHandlerBuilder MapFirst(t /// Maps a GET endpoint to a first DTO based on the applied filter. /// /// The type of the entity to be queried. + /// The type of the DTO to be returned. /// The type of the applied filter. /// The route builder. /// The route pattern. /// A builder for additional endpoint configuration. - public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + public static RouteHandlerBuilder MapSelectFirst(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern) where TEntity : class + where TDto : class where TFilter : class { - var first = new FirstEntityEndpoint(); + var first = new FirstModelEndpoint(); return builder.MapGet(pattern, first.First); } @@ -462,18 +464,20 @@ public static RouteHandlerBuilder MapFirstDto(this IEndpointRo /// Maps a GET endpoint to a first DTO based on the applied filter. /// /// The type of the entity to be queried. + /// The type of the DTO to be returned. /// The type of the applied filter. /// The route builder. /// The route pattern. /// Action to apply additional logic to the search criteria. /// A builder for additional endpoint configuration. - public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + public static RouteHandlerBuilder MapSelectFirst(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern, Action> searchAction) where TEntity : class + where TDto : class where TFilter : class { - var first = new FirstEntityEndpoint(searchAction); + var first = new FirstModelEndpoint(searchAction); return builder.MapGet(pattern, first.First); } @@ -481,19 +485,21 @@ public static RouteHandlerBuilder MapFirstDto(this IEndpointRo /// Maps a GET endpoint to a first DTO based on the applied filter with an additional identifier. ///
/// The type of the entity to be queried. + /// The type of the DTO to be returned. /// The type of the applied filter. /// The type of the first additional identifier. /// The route builder. /// The route pattern. /// Action to apply additional logic to the search criteria. /// A builder for additional endpoint configuration. - public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + public static RouteHandlerBuilder MapSelectFirst(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern, Action> searchAction) where TEntity : class + where TDto : class where TFilter : class { - var first = new FirstEntityEndpoint(searchAction); + var first = new FirstModelEndpoint(searchAction); return builder.MapGet(pattern, first.First); } @@ -501,6 +507,7 @@ public static RouteHandlerBuilder MapFirstDto(this IEndpo /// Maps a GET endpoint to a first DTO based on the applied filter with two additional identifiers. ///
/// The type of the entity to be queried. + /// The type of the DTO to be returned. /// The type of the applied filter. /// The type of the first additional identifier. /// The type of the second additional identifier. @@ -508,13 +515,14 @@ public static RouteHandlerBuilder MapFirstDto(this IEndpo /// The route pattern. /// Action to apply additional logic to the search criteria. /// A builder for additional endpoint configuration. - public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + public static RouteHandlerBuilder MapSelectFirst(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern, Action> searchAction) where TEntity : class + where TDto : class where TFilter : class { - var first = new FirstEntityEndpoint(searchAction); + var first = new FirstModelEndpoint(searchAction); return builder.MapGet(pattern, first.First); } @@ -522,6 +530,7 @@ public static RouteHandlerBuilder MapFirstDto(this /// Maps a GET endpoint to a first DTO based on the applied filter with three additional identifiers. ///
/// The type of the entity to be queried. + /// The type of the DTO to be returned. /// The type of the applied filter. /// The type of the first additional identifier. /// The type of the second additional identifier. @@ -530,13 +539,14 @@ public static RouteHandlerBuilder MapFirstDto(this /// The route pattern. /// Action to apply additional logic to the search criteria. /// A builder for additional endpoint configuration. - public static RouteHandlerBuilder MapFirstDto(this IEndpointRouteBuilder builder, + public static RouteHandlerBuilder MapSelectFirst(this IEndpointRouteBuilder builder, [StringSyntax("Route")] string pattern, Action> searchAction) where TEntity : class + where TDto : class where TFilter : class { - var first = new FirstEntityEndpoint(searchAction); + var first = new FirstModelEndpoint(searchAction); return builder.MapGet(pattern, first.First); } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EFSearchesExtensions.cs b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EFSearchesExtensions.cs new file mode 100644 index 0000000..5d6d21e --- /dev/null +++ b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EFSearchesExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using RoyalCode.SmartSearch; +using RoyalCode.SmartSearch.Defaults; +using RoyalCode.SmartSearch.EntityFramework.Services; +using RoyalCode.SmartSearch.Linq.Services; +using RoyalCode.SmartSearch.Linq.Sortings; + +namespace Microsoft.EntityFrameworkCore; + +/// +/// Extensions methods for to create . +/// +public static class EFSearchesExtensions +{ + /// + /// + /// Creates a new for the entity + /// using the used by the unit of work. + /// + /// + /// The entity type to create the criteria for. + /// The to use for performing the searches. + /// A new instance. + public static ICriteria Criteria(this DbContext db) + where TEntity : class + { + var specifierFactory = db.GetService(); + var orderByFactory = db.GetService(); + var selectorFactory = db.GetService(); + + var preparer = new CriteriaPerformer( + db, + specifierFactory, + orderByFactory, + selectorFactory); + + var criteria = new Criteria(preparer); + + return criteria; + } +} diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs index 8fea001..e84d2ee 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Extensions/EntityFrameworkSearchesServiceCollectionExtensions.cs @@ -1,13 +1,9 @@ using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection.Extensions; using RoyalCode.SmartSearch; -using RoyalCode.SmartSearch.Defaults; using RoyalCode.SmartSearch.EntityFramework.Configurations; using RoyalCode.SmartSearch.EntityFramework.Services; using RoyalCode.SmartSearch.Linq; -using RoyalCode.SmartSearch.Linq.Services; -using RoyalCode.SmartSearch.Linq.Sortings; namespace Microsoft.Extensions.DependencyInjection; @@ -72,31 +68,4 @@ public static IServiceCollection AddSearchManager(this IServiceColle return services; } - - /// - /// - /// Creates a new for the entity - /// using the used by the unit of work. - /// - /// - /// - /// - /// - public static ICriteria Criteria(this DbContext db) - where TEntity : class - { - var specifierFactory = db.GetService(); - var orderByFactory = db.GetService(); - var selectorFactory = db.GetService(); - - var preparer = new CriteriaPerformer( - db, - specifierFactory, - orderByFactory, - selectorFactory); - - var criteria = new Criteria(preparer); - - return criteria; - } } diff --git a/src/tests.targets b/src/tests.targets index 63f9365..136b8b1 100644 --- a/src/tests.targets +++ b/src/tests.targets @@ -11,9 +11,9 @@ 8.0.0 - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -22,7 +22,7 @@ all - + From 848b0d9d8aab6287d079c41da99b5621a2d592e0 Mon Sep 17 00:00:00 2001 From: eglauko Date: Sat, 19 Jul 2025 18:50:26 -0300 Subject: [PATCH 8/9] Rename solution to SmartSearch --- src/{RoyalCode.SmartSearch.sln => SmartSearch.sln} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{RoyalCode.SmartSearch.sln => SmartSearch.sln} (100%) diff --git a/src/RoyalCode.SmartSearch.sln b/src/SmartSearch.sln similarity index 100% rename from src/RoyalCode.SmartSearch.sln rename to src/SmartSearch.sln From 854f778e018a3027fb1468f8f79203a68d4b1659 Mon Sep 17 00:00:00 2001 From: eglauko Date: Sat, 19 Jul 2025 19:38:55 -0300 Subject: [PATCH 9/9] =?UTF-8?q?Refatora=C3=A7=C3=A3o=20de=20configura?= =?UTF-8?q?=C3=A7=C3=B5es=20e=20remo=C3=A7=C3=A3o=20de=20preview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Alterações na configuração de builds - Removido sufixo `-preview-3` do `SearchesPreview`. * Atualizações na interface de configurações de busca - Removido método `Add()` da interface `ISearchConfigurations`. - Alterado retorno do método para `ISearchConfigurations`. * Implementação do método `Add()` - Adicionada implementação do método em `SearchConfigurer` com `NotImplementedException`. - Atualizada implementação do método em `SearchConfigurations`. --- src/Directory.Build.props | 2 +- .../Configurations/ISearchConfigurations.cs | 11 +---------- .../Configurations/SearchConfigurations.cs | 4 +++- .../ISearchConfigurations.cs | 9 +++++++++ .../SpecifierFunctionGeneratorTests.cs | 8 +++++++- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 12de973..3752165 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,7 +5,7 @@ 0.8.0 - -preview-3 + 8.0.2 diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs index a1caa0d..77ddf75 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/ISearchConfigurations.cs @@ -9,13 +9,4 @@ namespace RoyalCode.SmartSearch.EntityFramework.Configurations; /// The type of the database context. public interface ISearchConfigurations : ISearchConfigurations where TDbContext : DbContext -{ - /// - /// Add a for an entity as a service, - /// related to used by the unit of work. - /// - /// The entity type. - /// The same instance. - ISearchConfigurations Add() - where TEntity : class; -} +{ } diff --git a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs index 15ab1c2..5b27533 100644 --- a/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs +++ b/src/RoyalCode.SmartSearch.EntityFramework/Configurations/SearchConfigurations.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using RoyalCode.SmartSearch.EntityFramework.Services; +using RoyalCode.SmartSearch.Linq; namespace RoyalCode.SmartSearch.EntityFramework.Configurations; @@ -20,7 +21,8 @@ public SearchConfigurations(IServiceCollection services) } /// - public ISearchConfigurations Add() where TEntity : class + public ISearchConfigurations Add() + where TEntity : class { // add criteria as a service for the respective context var serviceType = typeof(ICriteria<>).MakeGenericType(typeof(TEntity)); diff --git a/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs b/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs index 52226ad..5a2269a 100644 --- a/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs +++ b/src/RoyalCode.SmartSearch.Linq/ISearchConfigurations.cs @@ -125,5 +125,14 @@ ISearchConfigurations AddSelector( SelectorsMap.Instance.Add(selector); return this; } + + /// + /// Add a for an entity as a service, + /// related to the current search configurations and unit of work. + /// + /// The entity type. + /// The same instance. + ISearchConfigurations Add() + where TEntity : class; } diff --git a/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs b/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs index 1ff120d..ad2acdf 100644 --- a/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs +++ b/src/RoyalCode.SmartSearch.Tests/SpecifierFunctionGeneratorTests.cs @@ -522,7 +522,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } } -file class SearchConfigurer : ISearchConfigurations { } +file class SearchConfigurer : ISearchConfigurations +{ + public ISearchConfigurations Add() where TEntity : class + { + throw new NotImplementedException(); + } +} file class ConfigurableEntity {