diff --git a/README.md b/README.md index 8d92b7a..de80807 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Ooze.Typed 🌳💧🔧 -![Nuget](https://img.shields.io/nuget/v/Ooze.Typed) +[![Nuget](https://img.shields.io/nuget/v/Ooze.Typed)](https://www.nuget.org/packages/Ooze.Typed/) ![framework](https://img.shields.io/badge/framework-.net%206.0-green) ![framework](https://img.shields.io/badge/framework-.net%207.0-green) ![framework](https://img.shields.io/badge/framework-.net%208.0-green) @@ -43,11 +43,11 @@ public class Startup This call will register internal services needed by `Ooze` and will in turn return a related "builder" via which you can then register your `provider` implementations. ## Adding Filters 🗡️🧀 -After registering Ooze you need to create your filter definition. This can be done by implementing `IOozeFilterProvider` interface. After creating implementation you can use static `Filters` class to start of the builder which will in turn create your filter definitions. Example can be seen below: +After registering Ooze you need to create your filter definition. This can be done by implementing `IFilterProvider` interface. After creating implementation you can use static `Filters` class to start of the builder which will in turn create your filter definitions. Example can be seen below: ```csharp -public class MyClassFiltersProvider : IOozeFilterProvider +public class MyClassFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() { return Filters.CreateFor() //add equality filter onto MyClass instance over Id property and use Id property from incoming filter instance in that operation @@ -59,7 +59,7 @@ public class MyClassFiltersProvider : IOozeFilterProvider> GetFilters() +public IEnumerable> GetFilters() { return Filters.CreateFor() //check if property is equal to filter @@ -105,11 +105,11 @@ By default all the provider implementations that you register via `.Add` interface, and using `Sorters` static class to start of builder for creating sorters. Example of this can be found below: +by `Ooze` to sort your queries. This is done by implementing `ISorterProvider` interface, and using `Sorters` static class to start of builder for creating sorters. Example of this can be found below: ```csharp -public class MyClassSortersProvider : IOozeSorterProvider +public class MyClassSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() { return Sorters.CreateFor() //add sorting on Id property in provided direction from sorter instance @@ -153,12 +153,12 @@ query = resolver ``` ## Applying definitions 🧪 -In order to apply filter/sorter definitions you need to get instance of `IOozeTypedResolver`/`IOozeTypedResolver` after that you can just call methods in order to change `IQueryable` instance. Here is a more elaborate example below: +In order to apply filter/sorter definitions you need to get instance of `IOperationResolver`/`IOperationResolver` after that you can just call methods in order to change `IQueryable` instance. Here is a more elaborate example below: ```csharp //lets say you have a route which gets filters/sorters from request body app.MapPost("/", ( DatabaseContext db, - IOozeTypedResolver resolver, + IOperationResolver resolver, Input model) => { IQueryable query = db.Set(); @@ -178,6 +178,61 @@ public record class Input(MyEntityFilters Filters, MyEntitySorters Sorters, Pagi **NOTE:** Example before is bound to POST method, but you can use GET or anything else that suits you. For more elaborate example look [here](https://github.com/DenisPav/Ooze/tree/master/tests/Ooze.Typed.Web). Ooze only cares that you provide instances of your `filters`, `sorters` which will be then applied to `IQueryable` instances. +## Async support 🔃 +If needed you can opt in for the `async` version of the pipeline for the resolvers for different operations. In order to opt into async support you'll need to call `EnableAsyncResolvers()` call on the `IOozeServiceCollectionBuilder` which is exposed when calling `.AddOoze()` extension. Then you can just register providers as before via `.Add()` method. + +In order for `FilterProvider` or `SorterProvider` to be of `async` nature you need to use `IAsyncFilterProvider` or `IAsyncSorterProvider` interfaces. Accompanying `AsyncFilters` and `AsyncSorters` static classes are present to help you out with the building process as they are in non async version. + +In the end you'll need to use `IAsyncOperationResolver` istead of `IOperationResolver` and that should be it. Example of this can be seen below: +```csharp +//provider definition +public class MyEntityAsyncFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .Equal(entity => entity.Id, filter => filter.PostId) + .NotEqual(entity => entity.Id, filter => filter.NotEqualPostId) + .AddAsync(async filters => + { + await Task.CompletedTask; + return filters.Date != null; + }, async filters => + { + await Task.CompletedTask; + return entity => entity.Date == filters.Date; + }) + .Build()); +} + + +//in Program.cs or where your service collection registration is: +builder.Services.AddOozeTyped() + .EnableAsyncResolvers() + .Add(); + +//in your endpoints/controller actions/middlewares call the appropriate resolver methods: +[HttpPost("/sql-server")] +public async Task PostSqlServer( + [FromServices] IAsyncOperationResolver asyncResolver, + Input model) + { + IQueryable query = _sqlServerDb.Set(); + + query = await asyncResolver.WithQuery(query) + .Filter(model.Filters) + .Sort(model.Sorters) + .ApplyAsync(); + + var results = await query.ToListAsync(); + return Ok(results); + } + +record class Input(MyFilters Filters, IEnumerable Sorters, PagingOptions Paging); +``` + +**NOTE:** +`AsyncFilters/Sorters` builders will currently internally wrap the operations into `Tasks` even if they initially do not look like ones. + ## Advanced 🧠
@@ -188,7 +243,7 @@ Filter builders have a special parameter called `shouldRun` which is by default Example of this can be seen below: ```csharp -public IEnumerable> GetFilters() +public IEnumerable> GetFilters() { return Filters.CreateFor() //common filter definition, shouldRun is not used here but is resolved internally @@ -199,20 +254,20 @@ public IEnumerable> GetFilters() .Build(); } ``` -Due to nature of how `OozeFilterProvider` implementation work you can even create a custom filter collection +Due to nature of how `FilterProvider` implementation works you can even create a custom filter collection which will depend on a specific parameter being passed in the request. For example you could do something like next example (but you don't have to and I'm not sure why would you): ```csharp -public class BlogFiltersProvider : IOozeFilterProvider +public class BlogFiltersProvider : IFilterProvider { private readonly IHttpContextAccessor _httpContextAccessor; public BlogFiltersProvider(IHttpContextAccessor httpContextAccessor) => _httpContextAccessor = httpContextAccessor; - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() { var httpContext = _httpContextAccessor.HttpContext; var hasSecretParam = !httpContext?.Request.Query.ContainsKey("secret") ?? true; @@ -227,7 +282,7 @@ public class BlogFiltersProvider : IOozeFilterProvider } ``` -Similar can be applied to `OozeSorterProvider` implementations which also contain `shouldRun` parameter on +Similar can be applied to `SorterProvider` implementations which also contain `shouldRun` parameter on sorter builder extensions. Be careful when using `IHttpContextAccessor` in this way and be sure to read about how to correctly use it over on [this link](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AspNetCoreGuidance.md#do-not-store-ihttpcontextaccessorhttpcontext-in-a-field).
@@ -250,11 +305,11 @@ namespace Ooze.Typed.Web.Filters; public sealed class OozeFilter : IAsyncResultFilter where TEntity : class { - private readonly IOozeTypedResolver _resolver; + private readonly IOperationResolver _resolver; private readonly ILogger> _log; public OozeFilter( - IOozeTypedResolver resolver, + IOperationResolver resolver, ILogger> log) { _resolver = resolver; @@ -309,7 +364,7 @@ For more details look at sample project in `tests/Ooze.Typed.Web`.
Applying filters and sorters on IEnumerable collections -Due to nature of `IQueryable` and `IEnumerable` you can even use `OozeTypedResolver` on materialized or in memory collections for example `List`. You'll just need to convert it (cast it) to `IQueryable` via `.AsQueryable()` method. Notice that this can lead to exception since not all operations can be used this way. Some operations can't be used on `client side` and this can cause errors. +Due to nature of `IQueryable` and `IEnumerable` you can even use `OozeOperationResolver` on materialized or in memory collections for example `List`. You'll just need to convert it (cast it) to `IQueryable` via `.AsQueryable()` method. Notice that this can lead to exception since not all operations can be used this way. Some operations can't be used on `client side` and this can cause errors. Example of all this can be seen below: ```csharp diff --git a/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/AsyncFilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/AsyncFilterBuilderExtensions.cs new file mode 100644 index 0000000..0524dbd --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/AsyncFilterBuilderExtensions.cs @@ -0,0 +1,240 @@ +using System.Linq.Expressions; +using Ooze.Typed.Expressions; +using Ooze.Typed.Filters.Async; +using static System.Linq.Expressions.Expression; + +namespace Ooze.Typed.EntityFrameworkCore.MySql.Extensions; + +/// +/// MySql/MariaDb extensions for AsyncFilterBuilder +/// +public static class AsyncFilterBuilderExtensions +{ + /// + /// Creates DateDiffDay filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffDay( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffDayMethod, dataExpression, filterFunc, operation, diffConstant); + + /// + /// Creates DateDiffMonth filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMonth( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter( + Shared.DateDiffMonthMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffYear filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffYear( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter( + Shared.DateDiffYearMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffHour filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffHour( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter( + Shared.DateDiffHourMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffMinute filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMinute( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter( + Shared.DateDiffMinuteMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffSecond filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffSecond( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter( + Shared.DateDiffSecondMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffMicrosecond filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMicrosecond( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter( + Shared.DateDiffMicrosecondMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + private static IAsyncFilterBuilder IsDateDiffFilter( + this IAsyncFilterBuilder filterBuilder, + string methodName, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter) != null; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter).GetValueOrDefault(); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call( + Shared.DbFunctionsExtensionsType, + methodName, + Type.EmptyTypes, + Shared.EfPropertyExpression, + memberAccessExpression!, + constantExpression); + + var operationExpressionFactory = GetOperationFactory(operation); + var operationExpression = operationExpressionFactory(callExpression, Constant(diffConstant)); + return Lambda>(operationExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } + + private static Func GetOperationFactory(DateDiffOperation operation) + => operation switch + { + DateDiffOperation.GreaterThan => GreaterThan, + DateDiffOperation.LessThan => LessThan, + DateDiffOperation.Equal => Equal, + DateDiffOperation.NotEqual => (callExpr, constant) => Not(Equal(callExpr, constant)), + DateDiffOperation.NotGreaterThan => (callExpr, constant) => Not(GreaterThan(callExpr, constant)), + DateDiffOperation.NotLessThan => (callExpr, constant) => Not(LessThan(callExpr, constant)), + _ => throw new ArgumentOutOfRangeException(nameof(operation), operation, null) + }; +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/FilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/FilterBuilderExtensions.cs index 016e968..b643cb1 100644 --- a/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/FilterBuilderExtensions.cs +++ b/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/FilterBuilderExtensions.cs @@ -1,5 +1,4 @@ using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; using Ooze.Typed.Expressions; using Ooze.Typed.Filters; using static System.Linq.Expressions.Expression; @@ -11,16 +10,6 @@ namespace Ooze.Typed.EntityFrameworkCore.MySql.Extensions; /// public static class FilterBuilderExtensions { - private static readonly Type DbFunctionsExtensionsType = typeof(MySqlDbFunctionsExtensions); - private const string DateDiffDayMethod = nameof(MySqlDbFunctionsExtensions.DateDiffDay); - private const string DateDiffMonthMethod = nameof(MySqlDbFunctionsExtensions.DateDiffMonth); - private const string DateDiffYearMethod = nameof(MySqlDbFunctionsExtensions.DateDiffYear); - private const string DateDiffHourMethod = nameof(MySqlDbFunctionsExtensions.DateDiffHour); - private const string DateDiffMinuteMethod = nameof(MySqlDbFunctionsExtensions.DateDiffMinute); - private const string DateDiffSecondMethod = nameof(MySqlDbFunctionsExtensions.DateDiffSecond); - private const string DateDiffMicrosecondMethod = nameof(MySqlDbFunctionsExtensions.DateDiffMicrosecond); - private static readonly MemberExpression EfPropertyExpression = Property(null, typeof(EF), nameof(EF.Functions)); - /// /// Creates DateDiffDay filter over entity property and filter value /// @@ -38,7 +27,7 @@ public static IFilterBuilder IsDateDiffDay( Func filterFunc, DateDiffOperation operation, int diffConstant = 0) - => filterBuilder.IsDateDiffFilter(DateDiffDayMethod, dataExpression, filterFunc, operation, diffConstant); + => filterBuilder.IsDateDiffFilter(Shared.DateDiffDayMethod, dataExpression, filterFunc, operation, diffConstant); /// /// Creates DateDiffMonth filter over entity property and filter value @@ -60,7 +49,7 @@ public static IFilterBuilder IsDateDiffMonth int diffConstant = 0, Func? shouldRun = null) => filterBuilder.IsDateDiffFilter( - DateDiffMonthMethod, + Shared.DateDiffMonthMethod, dataExpression, filterFunc, operation, @@ -87,7 +76,7 @@ public static IFilterBuilder IsDateDiffYear( int diffConstant = 0, Func? shouldRun = null) => filterBuilder.IsDateDiffFilter( - DateDiffYearMethod, + Shared.DateDiffYearMethod, dataExpression, filterFunc, operation, @@ -114,7 +103,7 @@ public static IFilterBuilder IsDateDiffHour( int diffConstant = 0, Func? shouldRun = null) => filterBuilder.IsDateDiffFilter( - DateDiffHourMethod, + Shared.DateDiffHourMethod, dataExpression, filterFunc, operation, @@ -141,7 +130,7 @@ public static IFilterBuilder IsDateDiffMinute? shouldRun = null) => filterBuilder.IsDateDiffFilter( - DateDiffMinuteMethod, + Shared.DateDiffMinuteMethod, dataExpression, filterFunc, operation, @@ -168,7 +157,7 @@ public static IFilterBuilder IsDateDiffSecond? shouldRun = null) => filterBuilder.IsDateDiffFilter( - DateDiffSecondMethod, + Shared.DateDiffSecondMethod, dataExpression, filterFunc, operation, @@ -195,7 +184,7 @@ public static IFilterBuilder IsDateDiffMicrosecond? shouldRun = null) => filterBuilder.IsDateDiffFilter( - DateDiffMicrosecondMethod, + Shared.DateDiffMicrosecondMethod, dataExpression, filterFunc, operation, @@ -220,10 +209,10 @@ Expression> FilterExpressionFactory(TFilter filter) var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); var callExpression = Call( - DbFunctionsExtensionsType, + Shared.DbFunctionsExtensionsType, methodName, Type.EmptyTypes, - EfPropertyExpression, + Shared.EfPropertyExpression, memberAccessExpression!, constantExpression); diff --git a/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/Shared.cs b/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/Shared.cs new file mode 100644 index 0000000..32fd749 --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.MySql/Extensions/Shared.cs @@ -0,0 +1,18 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using static System.Linq.Expressions.Expression; + +namespace Ooze.Typed.EntityFrameworkCore.MySql.Extensions; + +internal static class Shared +{ + public static readonly Type DbFunctionsExtensionsType = typeof(MySqlDbFunctionsExtensions); + public const string DateDiffDayMethod = nameof(MySqlDbFunctionsExtensions.DateDiffDay); + public const string DateDiffMonthMethod = nameof(MySqlDbFunctionsExtensions.DateDiffMonth); + public const string DateDiffYearMethod = nameof(MySqlDbFunctionsExtensions.DateDiffYear); + public const string DateDiffHourMethod = nameof(MySqlDbFunctionsExtensions.DateDiffHour); + public const string DateDiffMinuteMethod = nameof(MySqlDbFunctionsExtensions.DateDiffMinute); + public const string DateDiffSecondMethod = nameof(MySqlDbFunctionsExtensions.DateDiffSecond); + public const string DateDiffMicrosecondMethod = nameof(MySqlDbFunctionsExtensions.DateDiffMicrosecond); + public static readonly MemberExpression EfPropertyExpression = Property(null, typeof(EF), nameof(EF.Functions)); +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/AsyncFilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/AsyncFilterBuilderExtensions.cs new file mode 100644 index 0000000..1c1d632 --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/AsyncFilterBuilderExtensions.cs @@ -0,0 +1,86 @@ +using System.Linq.Expressions; +using Ooze.Typed.Expressions; +using Ooze.Typed.Filters.Async; +using static System.Linq.Expressions.Expression; + +namespace Ooze.Typed.EntityFrameworkCore.Npgsql.Extensions; + +/// +/// Postgres extensions for AsyncFilterBuilder +/// +public static class AsyncFilterBuilderExtensions +{ + /// + /// Creates a ILike filter over specified property and filter + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Targeted property type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder InsensitiveLike( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter) != null; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.ILikeMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression, + constantExpression); + + return Lambda>(callExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } + + /// + /// Creates a Soundex filter over specified property and filter + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder SoundexEqual( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter) != null; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call(Shared.FuzzyStringMatchDbFunctionsExtensionsType, Shared.FuzzyStringMatchSoundexMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression); + var equalExpression = Equal(callExpression, constantExpression); + return Lambda>(equalExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } +} diff --git a/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/FilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/FilterBuilderExtensions.cs index 25f32bf..281cd5f 100644 --- a/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/FilterBuilderExtensions.cs +++ b/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/FilterBuilderExtensions.cs @@ -1,5 +1,4 @@ using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; using Ooze.Typed.Expressions; using Ooze.Typed.Filters; using static System.Linq.Expressions.Expression; @@ -11,13 +10,6 @@ namespace Ooze.Typed.EntityFrameworkCore.Npgsql.Extensions; /// public static class FilterBuilderExtensions { - private static readonly Type DbFunctionsExtensionsType = typeof(NpgsqlDbFunctionsExtensions); - private static readonly Type FuzzyStringMatchDbFunctionsExtensionsType = typeof(NpgsqlFuzzyStringMatchDbFunctionsExtensions); - // ReSharper disable once InconsistentNaming - private const string ILikeMethod = nameof(NpgsqlDbFunctionsExtensions.ILike); - private const string FuzzyStringMatchSoundexMethod = nameof(NpgsqlFuzzyStringMatchDbFunctionsExtensions.FuzzyStringMatchSoundex); - private static readonly MemberExpression EfPropertyExpression = Property(null, typeof(EF), nameof(EF.Functions)); - /// /// Creates a ILike filter over specified property and filter /// @@ -44,10 +36,10 @@ Expression> FilterExpressionFactory(TFilter filter) var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); var callExpression = Call( - DbFunctionsExtensionsType, - ILikeMethod, + Shared.DbFunctionsExtensionsType, + Shared.ILikeMethod, Type.EmptyTypes, - EfPropertyExpression, + Shared.EfPropertyExpression, memberAccessExpression, constantExpression); @@ -84,10 +76,10 @@ Expression> FilterExpressionFactory(TFilter filter) var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); var callExpression = Call( - FuzzyStringMatchDbFunctionsExtensionsType, - FuzzyStringMatchSoundexMethod, + Shared.FuzzyStringMatchDbFunctionsExtensionsType, + Shared.FuzzyStringMatchSoundexMethod, Type.EmptyTypes, - EfPropertyExpression, + Shared.EfPropertyExpression, memberAccessExpression); var equalExpression = Equal(callExpression, constantExpression); return Lambda>(equalExpression, parameterExpression); diff --git a/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/Shared.cs b/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/Shared.cs new file mode 100644 index 0000000..c080d9f --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.Npgsql/Extensions/Shared.cs @@ -0,0 +1,14 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Ooze.Typed.EntityFrameworkCore.Npgsql.Extensions; + +internal static class Shared +{ + // ReSharper disable once InconsistentNaming + public const string ILikeMethod = nameof(NpgsqlDbFunctionsExtensions.ILike); + public const string FuzzyStringMatchSoundexMethod = nameof(NpgsqlFuzzyStringMatchDbFunctionsExtensions.FuzzyStringMatchSoundex); + public static readonly Type DbFunctionsExtensionsType = typeof(NpgsqlDbFunctionsExtensions); + public static readonly Type FuzzyStringMatchDbFunctionsExtensionsType = typeof(NpgsqlFuzzyStringMatchDbFunctionsExtensions); + public static readonly MemberExpression EfPropertyExpression = Expression.Property(null, typeof(EF), nameof(EF.Functions)); +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/AsyncFilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/AsyncFilterBuilderExtensions.cs new file mode 100644 index 0000000..039fffd --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/AsyncFilterBuilderExtensions.cs @@ -0,0 +1,431 @@ +using System.Linq.Expressions; +using Ooze.Typed.Expressions; +using Ooze.Typed.Filters.Async; +using static System.Linq.Expressions.Expression; + +namespace Ooze.Typed.EntityFrameworkCore.SqlServer.Extensions; + +/// +/// Sql Server extensions for AsyncFilterBuilder +/// +public static class AsyncFilterBuilderExtensions +{ + /// + /// Creates a IsDate filter over a string property if filter requests it + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDate( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter).GetValueOrDefault() == true; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter).GetValueOrDefault(); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.IsDateMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression); + Expression notExpression = filterValue == true + ? callExpression + : Not(callExpression); + + return Lambda>(notExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } + + /// + /// Creates a IsNumeric filter over a string property if filter requests it + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsNumeric( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter).GetValueOrDefault() == true; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter).GetValueOrDefault(); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.IsNumericMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression); + Expression notExpression = filterValue == true + ? callExpression + : Not(callExpression); + + return Lambda>(notExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } + + /// + /// Creates a Contains filter over specified property and filter + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Targeted property type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder Contains( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter) != null; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.ContainsMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression, + constantExpression); + + return Lambda>(callExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } + + /// + /// Creates DateDiffDay filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffDay( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffDayMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffMonth filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMonth( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMonthMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffWeek filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffWeek( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffWeekMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffYear filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffYear( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffYearMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffHour filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffHour( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffHourMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffMinute filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMinute( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMinuteMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffSecond filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffSecond( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffSecondMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffMillisecond filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMillisecond( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMillisecondMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffMicrosecond filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffMicrosecond( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMicrosecondMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + /// + /// Creates DateDiffNanosecond filter over entity property and filter value + /// + /// Instance of + /// Expression targeting entity property + /// Filtering delegate targeting property with details if filter should apply + /// Operation defines which operations is applied over property and filter value + /// Optional diff constant to use in comparison + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder IsDateDiffNanosecond( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + => filterBuilder.IsDateDiffFilter(Shared.DateDiffNanosecondMethod, + dataExpression, + filterFunc, + operation, + diffConstant, + shouldRun); + + private static IAsyncFilterBuilder IsDateDiffFilter( + this IAsyncFilterBuilder filterBuilder, + string methodName, + Expression> dataExpression, + Func filterFunc, + DateDiffOperation operation, + int diffConstant = 0, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => filterFunc(filter) != null; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter).GetValueOrDefault(); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call(Shared.DbFunctionsExtensionsType, + methodName, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression, + constantExpression); + + var operationExpressionFactory = GetOperationFactory(operation); + var operationExpression = operationExpressionFactory(callExpression, Constant(diffConstant)); + return Lambda>(operationExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } + + private static Func GetOperationFactory(DateDiffOperation operation) + => operation switch + { + DateDiffOperation.GreaterThan => GreaterThan, + DateDiffOperation.LessThan => LessThan, + DateDiffOperation.Equal => Equal, + DateDiffOperation.NotEqual => (callExpr, constant) => Not(Equal(callExpr, constant)), + DateDiffOperation.NotGreaterThan => (callExpr, constant) => Not(GreaterThan(callExpr, constant)), + DateDiffOperation.NotLessThan => (callExpr, constant) => Not(LessThan(callExpr, constant)), + _ => throw new ArgumentOutOfRangeException(nameof(operation), operation, null) + }; +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/FilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/FilterBuilderExtensions.cs index 3991158..208ddd0 100644 --- a/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/FilterBuilderExtensions.cs +++ b/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/FilterBuilderExtensions.cs @@ -11,22 +11,6 @@ namespace Ooze.Typed.EntityFrameworkCore.SqlServer.Extensions; /// public static class FilterBuilderExtensions { - private static readonly Type DbFunctionsExtensionsType = typeof(SqlServerDbFunctionsExtensions); - private const string IsDateMethod = nameof(SqlServerDbFunctionsExtensions.IsDate); - private const string IsNumericMethod = nameof(SqlServerDbFunctionsExtensions.IsNumeric); - private const string ContainsMethod = nameof(SqlServerDbFunctionsExtensions.Contains); - private const string DateDiffDayMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffDay); - private const string DateDiffMonthMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMonth); - private const string DateDiffWeekMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffWeek); - private const string DateDiffYearMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffYear); - private const string DateDiffHourMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffHour); - private const string DateDiffMinuteMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMinute); - private const string DateDiffSecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffSecond); - private const string DateDiffMillisecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMillisecond); - private const string DateDiffMicrosecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMicrosecond); - private const string DateDiffNanosecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffNanosecond); - private static readonly MemberExpression EfPropertyExpression = Property(null, typeof(EF), nameof(EF.Functions)); - /// /// Creates a IsDate filter over a string property if filter requests it /// @@ -50,11 +34,8 @@ Expression> FilterExpressionFactory(TFilter filter) var filterValue = filterFunc(filter).GetValueOrDefault(); var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); - var callExpression = Call( - DbFunctionsExtensionsType, - IsDateMethod, - Type.EmptyTypes, - EfPropertyExpression, + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.IsDateMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, memberAccessExpression); Expression notExpression = filterValue == true ? callExpression @@ -91,11 +72,8 @@ Expression> FilterExpressionFactory(TFilter filter) var filterValue = filterFunc(filter).GetValueOrDefault(); var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); - var callExpression = Call( - DbFunctionsExtensionsType, - IsNumericMethod, - Type.EmptyTypes, - EfPropertyExpression, + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.IsNumericMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, memberAccessExpression); Expression notExpression = filterValue == true ? callExpression @@ -134,11 +112,8 @@ Expression> FilterExpressionFactory(TFilter filter) var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); - var callExpression = Call( - DbFunctionsExtensionsType, - ContainsMethod, - Type.EmptyTypes, - EfPropertyExpression, + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.ContainsMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, memberAccessExpression, constantExpression); @@ -169,8 +144,7 @@ public static IFilterBuilder IsDateDiffDay( DateDiffOperation operation, int diffConstant = 0, Func? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffDayMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffDayMethod, dataExpression, filterFunc, operation, @@ -196,8 +170,7 @@ public static IFilterBuilder IsDateDiffMonth DateDiffOperation operation, int diffConstant = 0, Func? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffMonthMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMonthMethod, dataExpression, filterFunc, operation, @@ -223,8 +196,7 @@ public static IFilterBuilder IsDateDiffWeek( DateDiffOperation operation, int diffConstant = 0, Func? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffWeekMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffWeekMethod, dataExpression, filterFunc, operation, @@ -250,8 +222,7 @@ public static IFilterBuilder IsDateDiffYear( DateDiffOperation operation, int diffConstant = 0, Func? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffYearMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffYearMethod, dataExpression, filterFunc, operation, @@ -277,8 +248,7 @@ public static IFilterBuilder IsDateDiffHour( DateDiffOperation operation, int diffConstant = 0, Func? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffHourMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffHourMethod, dataExpression, filterFunc, operation, @@ -304,8 +274,7 @@ public static IFilterBuilder IsDateDiffMinute? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffMinuteMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMinuteMethod, dataExpression, filterFunc, operation, @@ -331,8 +300,7 @@ public static IFilterBuilder IsDateDiffSecond? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffSecondMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffSecondMethod, dataExpression, filterFunc, operation, @@ -358,8 +326,7 @@ public static IFilterBuilder IsDateDiffMillisecond? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffMillisecondMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMillisecondMethod, dataExpression, filterFunc, operation, @@ -385,8 +352,7 @@ public static IFilterBuilder IsDateDiffMicrosecond? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffMicrosecondMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffMicrosecondMethod, dataExpression, filterFunc, operation, @@ -412,8 +378,7 @@ public static IFilterBuilder IsDateDiffNanosecond? shouldRun = null) - => filterBuilder.IsDateDiffFilter( - DateDiffNanosecondMethod, + => filterBuilder.IsDateDiffFilter(Shared.DateDiffNanosecondMethod, dataExpression, filterFunc, operation, @@ -437,11 +402,9 @@ Expression> FilterExpressionFactory(TFilter filter) var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); - var callExpression = Call( - DbFunctionsExtensionsType, + var callExpression = Call(Shared.DbFunctionsExtensionsType, methodName, - Type.EmptyTypes, - EfPropertyExpression, + Type.EmptyTypes, Shared.EfPropertyExpression, memberAccessExpression, constantExpression); diff --git a/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/Shared.cs b/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/Shared.cs new file mode 100644 index 0000000..498202a --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.SqlServer/Extensions/Shared.cs @@ -0,0 +1,24 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +// ReSharper disable InconsistentNaming + +namespace Ooze.Typed.EntityFrameworkCore.SqlServer.Extensions; + +internal static class Shared +{ + public const string IsDateMethod = nameof(SqlServerDbFunctionsExtensions.IsDate); + public const string IsNumericMethod = nameof(SqlServerDbFunctionsExtensions.IsNumeric); + public const string ContainsMethod = nameof(SqlServerDbFunctionsExtensions.Contains); + public const string DateDiffDayMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffDay); + public const string DateDiffMonthMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMonth); + public const string DateDiffWeekMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffWeek); + public const string DateDiffYearMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffYear); + public const string DateDiffHourMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffHour); + public const string DateDiffMinuteMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMinute); + public const string DateDiffSecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffSecond); + public const string DateDiffMillisecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMillisecond); + public const string DateDiffMicrosecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffMicrosecond); + public const string DateDiffNanosecondMethod = nameof(SqlServerDbFunctionsExtensions.DateDiffNanosecond); + public static readonly Type DbFunctionsExtensionsType = typeof(SqlServerDbFunctionsExtensions); + public static readonly MemberExpression EfPropertyExpression = Expression.Property(null, typeof(EF), nameof(EF.Functions)); +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/AsyncFilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/AsyncFilterBuilderExtensions.cs new file mode 100644 index 0000000..8e3177c --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/AsyncFilterBuilderExtensions.cs @@ -0,0 +1,49 @@ +using System.Linq.Expressions; +using Ooze.Typed.Expressions; +using Ooze.Typed.Filters.Async; +using static System.Linq.Expressions.Expression; + +namespace Ooze.Typed.EntityFrameworkCore.Sqlite.Extensions; + +/// +/// Sqlite extensions for AsyncFilterBuilder +/// +public static class AsyncFilterBuilderExtensions +{ + /// + /// Applies a Glob filter over specified entity property and passed filter glob expression + /// + /// Instance of + /// Expression targeting entity property for glob filtering + /// Filter delegate targeting property with glob expression + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder Glob( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => string.IsNullOrEmpty(filterFunc(filter)) == false; + + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.GlobMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression!, + constantExpression); + + return Lambda>(callExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/FilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/FilterBuilderExtensions.cs index d25695b..d74375a 100644 --- a/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/FilterBuilderExtensions.cs +++ b/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/FilterBuilderExtensions.cs @@ -1,5 +1,4 @@ using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; using Ooze.Typed.Expressions; using Ooze.Typed.Filters; using static System.Linq.Expressions.Expression; @@ -11,10 +10,6 @@ namespace Ooze.Typed.EntityFrameworkCore.Sqlite.Extensions; /// public static class FilterBuilderExtensions { - private static readonly Type DbFunctionsExtensionsType = typeof(SqliteDbFunctionsExtensions); - private const string GlobMethod = nameof(SqliteDbFunctionsExtensions.Glob); - private static readonly MemberExpression EfPropertyExpression = Property(null, typeof(EF), nameof(EF.Functions)); - /// /// Applies a Glob filter over specified entity property and passed filter glob expression /// @@ -39,11 +34,8 @@ Expression> FilterExpressionFactory(TFilter filter) var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); - var callExpression = Call( - DbFunctionsExtensionsType, - GlobMethod, - Type.EmptyTypes, - EfPropertyExpression, + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.GlobMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, memberAccessExpression!, constantExpression); diff --git a/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/Shared.cs b/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/Shared.cs new file mode 100644 index 0000000..8e7bbbd --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore.Sqlite/Extensions/Shared.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Ooze.Typed.EntityFrameworkCore.Sqlite.Extensions; + +internal static class Shared +{ + public const string GlobMethod = nameof(SqliteDbFunctionsExtensions.Glob); + public static readonly Type DbFunctionsExtensionsType = typeof(SqliteDbFunctionsExtensions); + public static readonly MemberExpression EfPropertyExpression = Expression.Property(null, typeof(EF), nameof(EF.Functions)); +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore/Extensions/AsyncFilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore/Extensions/AsyncFilterBuilderExtensions.cs new file mode 100644 index 0000000..df2640b --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore/Extensions/AsyncFilterBuilderExtensions.cs @@ -0,0 +1,48 @@ +using System.Linq.Expressions; +using Ooze.Typed.Expressions; +using Ooze.Typed.Filters.Async; +using static System.Linq.Expressions.Expression; + +namespace Ooze.Typed.EntityFrameworkCore.Extensions; + +/// +/// Entity framework extensions for AsyncFilterBuilder +/// +public static class AsyncFilterBuilderExtensions +{ + /// + /// Applies a like filter over specified entity property and passed filter like expression + /// + /// Instance of + /// Expression targeting entity property for like filtering + /// Filter delegate targeting property with like expression + /// Delegate returning bool value which denotes if filter should be applied + /// Entity type + /// Filter type + /// Instance of builder for fluent building of multiple filter definitions + public static IAsyncFilterBuilder Like( + this IAsyncFilterBuilder filterBuilder, + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + bool FilterShouldRun(TFilter filter) => string.IsNullOrEmpty(filterFunc(filter)) == false; + Expression> FilterExpressionFactory(TFilter filter) + { + var filterValue = filterFunc(filter); + var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); + var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); + var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.LikeMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, + memberAccessExpression!, + constantExpression); + + return Lambda>(callExpression, parameterExpression); + } + + shouldRun ??= FilterShouldRun; + filterBuilder.Add(shouldRun, FilterExpressionFactory); + return filterBuilder; + } +} \ No newline at end of file diff --git a/src/Ooze.Typed.EntityFrameworkCore/Extensions/FilterBuilderExtensions.cs b/src/Ooze.Typed.EntityFrameworkCore/Extensions/FilterBuilderExtensions.cs index 68a23c1..d8a9500 100644 --- a/src/Ooze.Typed.EntityFrameworkCore/Extensions/FilterBuilderExtensions.cs +++ b/src/Ooze.Typed.EntityFrameworkCore/Extensions/FilterBuilderExtensions.cs @@ -1,5 +1,4 @@ using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; using Ooze.Typed.Expressions; using Ooze.Typed.Filters; using static System.Linq.Expressions.Expression; @@ -11,10 +10,6 @@ namespace Ooze.Typed.EntityFrameworkCore.Extensions; /// public static class FilterBuilderExtensions { - private static readonly Type DbFunctionsExtensionsType = typeof(DbFunctionsExtensions); - private const string LikeMethod = nameof(DbFunctionsExtensions.Like); - private static readonly MemberExpression EfPropertyExpression = Property(null, typeof(EF), nameof(EF.Functions)); - /// /// Applies a like filter over specified entity property and passed filter like expression /// @@ -38,11 +33,8 @@ Expression> FilterExpressionFactory(TFilter filter) var memberAccessExpression = BasicExpressions.GetMemberExpression(dataExpression.Body); var parameterExpression = BasicExpressions.ExtractParameterExpression(memberAccessExpression); var constantExpression = BasicExpressions.GetWrappedConstantExpression(filterValue); - var callExpression = Call( - DbFunctionsExtensionsType, - LikeMethod, - Type.EmptyTypes, - EfPropertyExpression, + var callExpression = Call(Shared.DbFunctionsExtensionsType, Shared.LikeMethod, + Type.EmptyTypes, Shared.EfPropertyExpression, memberAccessExpression!, constantExpression); diff --git a/src/Ooze.Typed.EntityFrameworkCore/Extensions/Shared.cs b/src/Ooze.Typed.EntityFrameworkCore/Extensions/Shared.cs new file mode 100644 index 0000000..228351d --- /dev/null +++ b/src/Ooze.Typed.EntityFrameworkCore/Extensions/Shared.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Ooze.Typed.EntityFrameworkCore.Extensions; + +internal static class Shared +{ + public const string LikeMethod = nameof(DbFunctionsExtensions.Like); + public static readonly Type DbFunctionsExtensionsType = typeof(DbFunctionsExtensions); + public static readonly MemberExpression EfPropertyExpression = Expression.Property(null, typeof(EF), nameof(EF.Functions)); +} \ No newline at end of file diff --git a/src/Ooze.Typed/AsyncOperationResolver.cs b/src/Ooze.Typed/AsyncOperationResolver.cs new file mode 100644 index 0000000..d39bdce --- /dev/null +++ b/src/Ooze.Typed/AsyncOperationResolver.cs @@ -0,0 +1,184 @@ +using System.Linq.Expressions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Ooze.Typed.Filters; +using Ooze.Typed.Filters.Async; +using Ooze.Typed.Paging; +using Ooze.Typed.Sorters; +using Ooze.Typed.Sorters.Async; + +namespace Ooze.Typed; + +/// +internal class AsyncOperationResolver( + IServiceProvider serviceProvider, + ILogger log) + : IAsyncOperationResolver +{ + public async ValueTask> FilterAsync( + IQueryable query, + TFilters? filters) + { + if (filters is null) + { + log.LogDebug("Filters of type: [{typeName}] are null", typeof(TFilters).Name); + return query; + } + + var filterHandler = serviceProvider.GetRequiredService>(); + query = await filterHandler.ApplyAsync(query, filters) + .ConfigureAwait(false); + + return query; + } + + public async ValueTask> SortAsync( + IQueryable query, + IEnumerable? sorters) + { + sorters ??= Enumerable.Empty(); + if (sorters.Any() == false) + { + log.LogDebug("Sorters of type: [{typeName}] are not present", typeof(TSorters).Name); + return query; + } + + var sorterHandler = serviceProvider.GetRequiredService>(); + query = await sorterHandler.ApplyAsync(query, sorters) + .ConfigureAwait(false); + + return query; + } + + public ValueTask> PageAsync( + IQueryable query, + PagingOptions? pagingOptions) + { + if (pagingOptions == null) + { + log.LogDebug("Pagination options are not present"); + return ValueTask.FromResult(query); + } + + var pagingHandler = serviceProvider.GetRequiredService>(); + query = pagingHandler.Apply(query, pagingOptions); + + return ValueTask.FromResult(query); + } + + public ValueTask> PageWithCursorAsync( + IQueryable query, + Expression> cursorPropertyExpression, + CursorPagingOptions? pagingOptions) + { + if (pagingOptions == null) + { + log.LogDebug("Pagination options are not present"); + return ValueTask.FromResult(query); + } + + var sorterHandler = serviceProvider.GetRequiredService>(); + query = sorterHandler.ApplyCursor(query, cursorPropertyExpression, pagingOptions); + + return ValueTask.FromResult(query); + } +} + +/// +internal class AsyncOperationResolver( + IAsyncSorterHandler sorterHandler, + IAsyncFilterHandler filterHandler, + IOozePagingHandler pagingHandler, + ILogger> log) + : IAsyncOperationResolver +{ + private OozeResolverData _resolverData = new(); + + public IAsyncOperationResolver WithQuery(IQueryable query) + { + _resolverData = _resolverData with + { + Query = query + }; + + return this; + } + + public IAsyncOperationResolver Sort(IEnumerable? sorters) + { + _resolverData = _resolverData with + { + Sorters = sorters + }; + + return this; + } + + public IAsyncOperationResolver Filter(TFilters? filters) + { + _resolverData = _resolverData with + { + Filters = filters + }; + + return this; + } + + public IAsyncOperationResolver Page(PagingOptions? pagingOptions) + { + _resolverData = _resolverData with + { + Paging = pagingOptions + }; + + return this; + } + + public async ValueTask> ApplyAsync() + { + if (_resolverData.Query is null) + throw new Exception("Queryable not defined/passed to the resolver!"); + + if (_resolverData.Sorters is { } sorters) + { + log.LogDebug("Applying sorters: [{typeName}]", typeof(TSorters).Name); + + _resolverData = _resolverData with + { + Query = await sorterHandler.ApplyAsync(_resolverData.Query, sorters) + .ConfigureAwait(false) + }; + } + + if (_resolverData.Filters is { } filters) + { + log.LogDebug("Applying filters: [{typeName}]", typeof(TFilters).Name); + + _resolverData = _resolverData with + { + Query = await filterHandler.ApplyAsync(_resolverData.Query, filters) + .ConfigureAwait(false) + }; + } + + + if (_resolverData.Paging is { } paging) + { + log.LogDebug("Applying pagination options"); + + _resolverData = _resolverData with + { + Query = pagingHandler.Apply(_resolverData.Query, paging) + }; + } + + + return _resolverData.Query; + } +} + +internal record OozeResolverData( + IQueryable? Query = default, + TFilter? Filters = default, + IEnumerable? Sorters = default, + PagingOptions? Paging = default); \ No newline at end of file diff --git a/src/Ooze.Typed/Extensions/IOozeServiceCollectionBuilder.cs b/src/Ooze.Typed/Extensions/IOozeServiceCollectionBuilder.cs index 562e530..17f6062 100644 --- a/src/Ooze.Typed/Extensions/IOozeServiceCollectionBuilder.cs +++ b/src/Ooze.Typed/Extensions/IOozeServiceCollectionBuilder.cs @@ -14,4 +14,10 @@ public interface IOozeServiceCollectionBuilder /// Type of provider implementation /// Builder instance IOozeServiceCollectionBuilder Add(ServiceLifetime providerLifetime = ServiceLifetime.Singleton); + + /// + /// Enable usage of async resolvers and filter/sorter registrations + /// + /// Builder instance + IOozeServiceCollectionBuilder EnableAsyncResolvers(); } diff --git a/src/Ooze.Typed/Extensions/OozeServiceCollectionBuilder.cs b/src/Ooze.Typed/Extensions/OozeServiceCollectionBuilder.cs index 9fbaf42..2577c10 100644 --- a/src/Ooze.Typed/Extensions/OozeServiceCollectionBuilder.cs +++ b/src/Ooze.Typed/Extensions/OozeServiceCollectionBuilder.cs @@ -1,15 +1,19 @@ using Microsoft.Extensions.DependencyInjection; using Ooze.Typed.Filters; +using Ooze.Typed.Filters.Async; using Ooze.Typed.Paging; using Ooze.Typed.Sorters; +using Ooze.Typed.Sorters.Async; namespace Ooze.Typed.Extensions; /// internal class OozeServiceCollectionBuilder(IServiceCollection services) : IOozeServiceCollectionBuilder { - private static readonly Type FilterProviderType = typeof(IOozeFilterProvider<,>); - private static readonly Type SorterProviderType = typeof(IOozeSorterProvider<,>); + private static readonly Type FilterProviderType = typeof(IFilterProvider<,>); + private static readonly Type SorterProviderType = typeof(ISorterProvider<,>); + private static readonly Type AsyncFilterProviderType = typeof(IAsyncFilterProvider<,>); + private static readonly Type AsyncSorterProviderType = typeof(IAsyncSorterProvider<,>); public IOozeServiceCollectionBuilder Add(ServiceLifetime providerLifetime = ServiceLifetime.Singleton) { @@ -19,8 +23,13 @@ public IOozeServiceCollectionBuilder Add(ServiceLifetime providerLife .ToList(); var filterProvider = implementedInterfaces.SingleOrDefault(@interface => CheckTypePredicate(@interface, FilterProviderType)); var sorterProvider = implementedInterfaces.SingleOrDefault(@interface => CheckTypePredicate(@interface, SorterProviderType)); + var asyncFilterProvider = implementedInterfaces.SingleOrDefault(@interface => CheckTypePredicate(@interface, AsyncFilterProviderType)); + var asyncSorterProvider = implementedInterfaces.SingleOrDefault(@interface => CheckTypePredicate(@interface, AsyncSorterProviderType)); - if (filterProvider is null && sorterProvider is null) + if (filterProvider is null + && sorterProvider is null + && asyncFilterProvider is null + && asyncSorterProvider is null) { throw new ArgumentException("Passed Type is not valid Ooze provider", nameof(TProvider)); } @@ -29,10 +38,28 @@ public IOozeServiceCollectionBuilder Add(ServiceLifetime providerLife { services.Add(new ServiceDescriptor(filterProvider, providerType, providerLifetime)); } + if (asyncFilterProvider is not null) + { + services.Add(new ServiceDescriptor(asyncFilterProvider, providerType, providerLifetime)); + } if (sorterProvider is not null) { services.Add(new ServiceDescriptor(sorterProvider, providerType, providerLifetime)); } + if (asyncSorterProvider is not null) + { + services.Add(new ServiceDescriptor(asyncSorterProvider, providerType, providerLifetime)); + } + + return this; + } + + public IOozeServiceCollectionBuilder EnableAsyncResolvers() + { + services.AddScoped(); + services.AddScoped(typeof(IAsyncOperationResolver<,,>), typeof(AsyncOperationResolver<,,>)); + services.AddScoped(typeof(IAsyncFilterHandler<,>), typeof(AsyncFilterHandler<,>)); + services.AddScoped(typeof(IAsyncSorterHandler<,>), typeof(AsyncSorterHandler<,>)); return this; } @@ -42,10 +69,10 @@ private static bool CheckTypePredicate(Type interfaceType, Type providerType) internal IOozeServiceCollectionBuilder AddCommonServices() { - services.AddScoped(); - services.AddScoped(typeof(IOozeTypedResolver<,,>), typeof(OozeTypedResolver<,,>)); - services.AddScoped(typeof(IOozeFilterHandler<,>), typeof(OozeFilterHandler<,>)); - services.AddScoped(typeof(IOozeSorterHandler<,>), typeof(OozeSorterHandler<,>)); + services.AddScoped(); + services.AddScoped(typeof(IOperationResolver<,,>), typeof(OperationResolver<,,>)); + services.AddScoped(typeof(IFilterHandler<,>), typeof(FilterHandler<,>)); + services.AddScoped(typeof(ISorterHandler<,>), typeof(SorterHandler<,>)); services.AddScoped(typeof(IOozePagingHandler<>), typeof(OozePagingHandler<>)); return this; diff --git a/src/Ooze.Typed/Filters/Async/AsyncFilterBuilder.cs b/src/Ooze.Typed/Filters/Async/AsyncFilterBuilder.cs new file mode 100644 index 0000000..aea3e09 --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/AsyncFilterBuilder.cs @@ -0,0 +1,276 @@ +using System.Linq.Expressions; +using Ooze.Typed.Expressions; + +namespace Ooze.Typed.Filters.Async; + +/// +internal class AsyncFilterBuilder : IAsyncFilterBuilder +{ + private readonly IList> _filterDefinitions = + new List>(); + + public IAsyncFilterBuilder Equal( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => filterFunc(filter) != null; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filter => Task.FromResult(shouldRun(filter)), + FilterExpressionFactory = filter => + Task.FromResult(BasicExpressions.Equal( + dataExpression, + filterFunc(filter))) + }); + + return this; + } + + public IAsyncFilterBuilder NotEqual( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => filterFunc(filter) != null; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => Task.FromResult(BasicExpressions.NotEqual( + dataExpression, + filterFunc(filter))) + }); + + return this; + } + + public IAsyncFilterBuilder GreaterThan( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => filterFunc(filter) != null; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => + Task.FromResult(BasicExpressions.GreaterThan( + dataExpression, + filterFunc(filter))) + }); + + return this; + } + + public IAsyncFilterBuilder LessThan( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => filterFunc(filter) != null; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => + Task.FromResult(BasicExpressions.LessThan( + dataExpression, + filterFunc(filter))) + }); + + return this; + } + + public IAsyncFilterBuilder In( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => + { + var value = filterFunc(filter); + return value != null && value.Any(); + }; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => Task.FromResult(BasicExpressions.In( + dataExpression, + filterFunc(filter))) + }); + + return this; + } + + public IAsyncFilterBuilder NotIn( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => + { + var value = filterFunc(filter); + return value != null && value.Any(); + }; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => + Task.FromResult(BasicExpressions.In( + dataExpression, + filterFunc(filter), + true)) + }); + + return this; + } + + public IAsyncFilterBuilder Range( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => + { + var value = filterFunc(filter); + return value != null && value.From != null && value.To != null; + }; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => + Task.FromResult(BasicExpressions.Range( + dataExpression, + filterFunc(filter))) + }); + + return this; + } + + public IAsyncFilterBuilder OutOfRange( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => + { + var value = filterFunc(filter); + return value != null && value.From != null && value.To != null; + }; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => + Task.FromResult(BasicExpressions.Range( + dataExpression, + filterFunc(filter), + true)) + }); + + return this; + } + + public IAsyncFilterBuilder StartsWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => string.IsNullOrEmpty(filterFunc(filter)) == false; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter + => Task.FromResult(BasicExpressions.StringOperation( + dataExpression, + filterFunc(filter), + CommonMethods.StringStartsWith)) + }); + + return this; + } + + public IAsyncFilterBuilder DoesntStartWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => string.IsNullOrEmpty(filterFunc(filter)) == false; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter => Task.FromResult(BasicExpressions.StringOperation( + dataExpression, + filterFunc(filter), + CommonMethods.StringStartsWith, + true)) + }); + + return this; + } + + public IAsyncFilterBuilder EndsWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => string.IsNullOrEmpty(filterFunc(filter)) == false; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter + => Task.FromResult(BasicExpressions.StringOperation( + dataExpression, + filterFunc(filter), + CommonMethods.StringEndsWith)) + }); + + return this; + } + + public IAsyncFilterBuilder DoesntEndWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null) + { + shouldRun ??= filter => string.IsNullOrEmpty(filterFunc(filter)) == false; + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filter + => Task.FromResult(BasicExpressions.StringOperation( + dataExpression, + filterFunc(filter), + CommonMethods.StringEndsWith, + true)) + }); + + return this; + } + + public IAsyncFilterBuilder Add( + Func shouldRun, + Func>> filterExpressionFactory) + { + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = filters => Task.FromResult(shouldRun(filters)), + FilterExpressionFactory = filters => Task.FromResult(filterExpressionFactory(filters)) + }); + + return this; + } + + public IAsyncFilterBuilder AddAsync( + Func> shouldRun, + Func>>> filterExpressionFactory) + { + _filterDefinitions.Add(new AsyncFilterDefinition + { + ShouldRun = shouldRun, + FilterExpressionFactory = filterExpressionFactory + }); + + return this; + } + + public IEnumerable> Build() => _filterDefinitions; +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Async/AsyncFilterDefinition.cs b/src/Ooze.Typed/Filters/Async/AsyncFilterDefinition.cs new file mode 100644 index 0000000..47b68eb --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/AsyncFilterDefinition.cs @@ -0,0 +1,21 @@ +using System.Linq.Expressions; + +namespace Ooze.Typed.Filters.Async; + +/// +/// Represents a single async filter definition +/// +/// Entity type +/// Filter type +public class AsyncFilterDefinition +{ + /// + /// Delegate which decides if the filter should be applied + /// + public Func> ShouldRun { get; init; } = null!; + + /// + /// Delegate which creates final expression used for filtering + /// + public Func>>> FilterExpressionFactory { get; init; } = null!; +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Async/AsyncFilterHandler.cs b/src/Ooze.Typed/Filters/Async/AsyncFilterHandler.cs new file mode 100644 index 0000000..ed0fd00 --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/AsyncFilterHandler.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Logging; + +namespace Ooze.Typed.Filters.Async; + +internal class AsyncFilterHandler( + IEnumerable> filterProviders, + ILogger> log) + : IAsyncFilterHandler +{ + public async ValueTask> ApplyAsync( + IQueryable query, + TFilters filters) + { + log.LogDebug("Processing available filters!"); + + var filterDefinitions = new List>(); + foreach (var provider in filterProviders) + { + var definitions = await provider.GetFiltersAsync() + .ConfigureAwait(false); + filterDefinitions.AddRange(definitions); + } + + foreach (var filterDefinition in filterDefinitions) + { + var shouldRun = await filterDefinition.ShouldRun(filters) + .ConfigureAwait(false); + if (shouldRun == false) + continue; + + var filterExpr = await filterDefinition.FilterExpressionFactory + .Invoke(filters) + .ConfigureAwait(false); + if (filterExpr is null) + continue; + + log.LogDebug("Applying filter: [{@filter}]", filterExpr); + query = query.Where(filterExpr); + } + + return query; + } +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Async/AsyncFilters.cs b/src/Ooze.Typed/Filters/Async/AsyncFilters.cs new file mode 100644 index 0000000..0cd70ba --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/AsyncFilters.cs @@ -0,0 +1,16 @@ +namespace Ooze.Typed.Filters.Async; + +/// +/// Static class with helper methods to start building async filter definitions for specified entity type and filter type +/// +public static class AsyncFilters +{ + /// + /// Creates a new instance of async filter builder for passed entity type and filter type + /// + /// Entity type + /// Filter type + /// Filter builder instance for entity, filter type combination + public static IAsyncFilterBuilder CreateFor() + => new AsyncFilterBuilder(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Async/IAsyncFilterBuilder.cs b/src/Ooze.Typed/Filters/Async/IAsyncFilterBuilder.cs new file mode 100644 index 0000000..8200d50 --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/IAsyncFilterBuilder.cs @@ -0,0 +1,27 @@ +using System.Linq.Expressions; + +namespace Ooze.Typed.Filters.Async; + +/// +/// Interface defining contract for async filter builder implementation +/// +/// Entity type +/// Filter type +public interface IAsyncFilterBuilder : IFilters> +{ + /// + /// Fluently creates custom async filter definition + /// + /// Delegate showing if the filter should execute + /// Factory for creation of filter Expression + /// Instance of builder for fluent building of multiple filter definitions + IAsyncFilterBuilder AddAsync( + Func> shouldRun, + Func>>> filterExpressionFactory); + + /// + /// Return collection of created async filter definitions + /// + /// Collection of filter definitions + IEnumerable> Build(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Async/IAsyncFilterHandler.cs b/src/Ooze.Typed/Filters/Async/IAsyncFilterHandler.cs new file mode 100644 index 0000000..1227d48 --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/IAsyncFilterHandler.cs @@ -0,0 +1,8 @@ +namespace Ooze.Typed.Filters.Async; + +internal interface IAsyncFilterHandler +{ + ValueTask> ApplyAsync( + IQueryable query, + TFilter filters); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Async/IAsyncFilterProvider.cs b/src/Ooze.Typed/Filters/Async/IAsyncFilterProvider.cs new file mode 100644 index 0000000..e98ee01 --- /dev/null +++ b/src/Ooze.Typed/Filters/Async/IAsyncFilterProvider.cs @@ -0,0 +1,17 @@ +namespace Ooze.Typed.Filters.Async; + +/// +/// Async filter provider interface called internally by / +/// to fetch defined filters for provided Entity type. +/// +/// Entity type +/// Filter implementation type +public interface IAsyncFilterProvider +{ + /// + /// Method used for creation of definitions. These definitions are + /// used in filtering process. + /// + /// Collection of filter definitions + ValueTask>> GetFiltersAsync(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/FilterBuilder.cs b/src/Ooze.Typed/Filters/FilterBuilder.cs index 9799541..d1ef9c7 100644 --- a/src/Ooze.Typed/Filters/FilterBuilder.cs +++ b/src/Ooze.Typed/Filters/FilterBuilder.cs @@ -224,5 +224,5 @@ public IFilterBuilder Add( return this; } - public IEnumerable> Build() => _filterDefinitions; + public IEnumerable> Build() => _filterDefinitions; } \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/FilterDefinition.cs b/src/Ooze.Typed/Filters/FilterDefinition.cs index 6a219a3..49bd4bb 100644 --- a/src/Ooze.Typed/Filters/FilterDefinition.cs +++ b/src/Ooze.Typed/Filters/FilterDefinition.cs @@ -2,8 +2,20 @@ namespace Ooze.Typed.Filters; -internal class FilterDefinition : IFilterDefinition +/// +/// Represents a single filter definition +/// +/// Entity type +/// Filter type +public class FilterDefinition { - public Func ShouldRun { get; internal set; } = null!; - public Func>> FilterExpressionFactory { get; internal set; } = null!; + /// + /// Delegate which decides if the filter should be applied + /// + public Func ShouldRun { get; set; } = null!; + + /// + /// Delegate which creates final expression used for filtering + /// + public Func>> FilterExpressionFactory { get; set; } = null!; } \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/OozeFilterHandler.cs b/src/Ooze.Typed/Filters/FilterHandler.cs similarity index 54% rename from src/Ooze.Typed/Filters/OozeFilterHandler.cs rename to src/Ooze.Typed/Filters/FilterHandler.cs index 8d9430b..5fcc211 100644 --- a/src/Ooze.Typed/Filters/OozeFilterHandler.cs +++ b/src/Ooze.Typed/Filters/FilterHandler.cs @@ -2,10 +2,10 @@ namespace Ooze.Typed.Filters; -internal class OozeFilterHandler( - IEnumerable> filterProviders, - ILogger> log) - : IOozeFilterHandler +internal class FilterHandler( + IEnumerable> filterProviders, + ILogger> log) + : IFilterHandler { public IQueryable Apply( IQueryable query, @@ -14,19 +14,18 @@ public IQueryable Apply( log.LogDebug("Processing available filters!"); var validFilters = filterProviders.SelectMany(provider => provider.GetFilters()) - .Cast>() .Where(filter => filter.ShouldRun(filters)); foreach (var filterDefinition in validFilters) { var filterExpr = filterDefinition.FilterExpressionFactory?.Invoke(filters); - if (filterExpr is not null) - { - log.LogDebug("Applying filter: [{@filter}]", filterExpr); - query = query.Where(filterExpr); - } + if (filterExpr is null) + continue; + + log.LogDebug("Applying filter: [{@filter}]", filterExpr); + query = query.Where(filterExpr); } return query; } -} +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/Filters.cs b/src/Ooze.Typed/Filters/Filters.cs index 687baca..57d3fe5 100644 --- a/src/Ooze.Typed/Filters/Filters.cs +++ b/src/Ooze.Typed/Filters/Filters.cs @@ -13,4 +13,4 @@ public static class Filters /// Filter builder instance for entity, filter type combination public static IFilterBuilder CreateFor() => new FilterBuilder(); -} +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/IFilterBuilder.cs b/src/Ooze.Typed/Filters/IFilterBuilder.cs index b7458f6..f3ea1a3 100644 --- a/src/Ooze.Typed/Filters/IFilterBuilder.cs +++ b/src/Ooze.Typed/Filters/IFilterBuilder.cs @@ -1,179 +1,15 @@ -using System.Linq.Expressions; - -namespace Ooze.Typed.Filters; +namespace Ooze.Typed.Filters; /// /// Interface defining contract for filter builder implementation /// /// Entity type /// Filter type -public interface IFilterBuilder +public interface IFilterBuilder : IFilters> { - /// - /// Fluently creates "Equality" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder Equal( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Not Equal" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder NotEqual( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Greater Than" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder GreaterThan( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Less Than" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder LessThan( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "In" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder In( - Expression> dataExpression, - Func?> filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Not In" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder NotIn( - Expression> dataExpression, - Func?> filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Range" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder Range( - Expression> dataExpression, - Func?> filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Out of Range" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Target type of entity property - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder OutOfRange( - Expression> dataExpression, - Func?> filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Starts with" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder StartsWith( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Doesn't Start With" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder DoesntStartWith( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Ends With" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder EndsWith( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates "Doesn't End With" filter definition - /// - /// Expression targeting property of specified entity class - /// Delegate targeting property of filter class - /// Delegate returning bool value which denotes if filter should be applied - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder DoesntEndWith( - Expression> dataExpression, - Func filterFunc, - Func? shouldRun = null); - - /// - /// Fluently creates custom filter definition - /// - /// Delegate showing if the filter should execute - /// Factory for creation of filter Expression - /// Instance of builder for fluent building of multiple filter definitions - IFilterBuilder Add( - Func shouldRun, - Func>> filterExpressionFactory); - /// /// Return collection of created filter definitions /// /// Collection of filter definitions - IEnumerable> Build(); -} + IEnumerable> Build(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/IOozeFilterHandler.cs b/src/Ooze.Typed/Filters/IFilterHandler.cs similarity index 67% rename from src/Ooze.Typed/Filters/IOozeFilterHandler.cs rename to src/Ooze.Typed/Filters/IFilterHandler.cs index 5f05232..478f204 100644 --- a/src/Ooze.Typed/Filters/IOozeFilterHandler.cs +++ b/src/Ooze.Typed/Filters/IFilterHandler.cs @@ -1,8 +1,8 @@ namespace Ooze.Typed.Filters; -internal interface IOozeFilterHandler +internal interface IFilterHandler { IQueryable Apply( IQueryable query, TFilter filters); -} +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/IOozeFilterProvider.cs b/src/Ooze.Typed/Filters/IFilterProvider.cs similarity index 72% rename from src/Ooze.Typed/Filters/IOozeFilterProvider.cs rename to src/Ooze.Typed/Filters/IFilterProvider.cs index de2f409..e7bbfce 100644 --- a/src/Ooze.Typed/Filters/IOozeFilterProvider.cs +++ b/src/Ooze.Typed/Filters/IFilterProvider.cs @@ -1,17 +1,17 @@ namespace Ooze.Typed.Filters; /// -/// Filter provider interface called internally by / -/// to fetch defined filters for provided Entity type. +/// Filter provider interface called internally by / +/// to fetch defined filters for provided Entity type. /// /// Entity type /// Filter implementation type -public interface IOozeFilterProvider +public interface IFilterProvider { /// /// Method used for creation of definitions. These definitions are /// used in filtering process. /// /// Collection of filter definitions - IEnumerable> GetFilters(); -} + IEnumerable> GetFilters(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Filters/IFilters.cs b/src/Ooze.Typed/Filters/IFilters.cs new file mode 100644 index 0000000..121ff5d --- /dev/null +++ b/src/Ooze.Typed/Filters/IFilters.cs @@ -0,0 +1,174 @@ +using System.Linq.Expressions; + +namespace Ooze.Typed.Filters; + +/// +/// Interface with shared filter contracts +/// +/// Entity type +/// Filter type +/// Return type +public interface IFilters +{ + /// + /// Fluently creates "Equality" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn Equal( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Not Equal" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn NotEqual( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Greater Than" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn GreaterThan( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Less Than" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn LessThan( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "In" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn In( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Not In" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn NotIn( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Range" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn Range( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Out of Range" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Target type of entity property + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn OutOfRange( + Expression> dataExpression, + Func?> filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Starts with" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn StartsWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Doesn't Start With" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn DoesntStartWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Ends With" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn EndsWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates "Doesn't End With" filter definition + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of filter class + /// Delegate returning bool value which denotes if filter should be applied + /// Instance of builder for fluent building of multiple filter definitions + TReturn DoesntEndWith( + Expression> dataExpression, + Func filterFunc, + Func? shouldRun = null); + + /// + /// Fluently creates custom filter definition + /// + /// Delegate showing if the filter should execute + /// Factory for creation of filter Expression + /// Instance of builder for fluent building of multiple filter definitions + TReturn Add( + Func shouldRun, + Func>> filterExpressionFactory); +} \ No newline at end of file diff --git a/src/Ooze.Typed/IAsyncOperationResolver.cs b/src/Ooze.Typed/IAsyncOperationResolver.cs new file mode 100644 index 0000000..fe0204f --- /dev/null +++ b/src/Ooze.Typed/IAsyncOperationResolver.cs @@ -0,0 +1,109 @@ +using System.Linq.Expressions; +using Ooze.Typed.Paging; + +namespace Ooze.Typed; + +/// +/// Ooze resolver instance, contains implementations of Filtering, Sorting and Paging methods used to update +/// instances. +/// +public interface IAsyncOperationResolver +{ + /// + /// Applies valid sorters over instance. Sorters application is based of sorter provider + /// implementation for entity type + /// + /// Base instance + /// Sorter definitions to apply over instance + /// Entity type + /// Sorter implementation type + /// Updated instance + ValueTask> SortAsync( + IQueryable query, + IEnumerable sorters); + + /// + /// Applies valid filters over instance. Filter application is based of filter provider + /// implementation for entity type + /// + /// Base instance + /// Filter definitions to apply over instance + /// Entity type + /// Filter implementation type + /// Updated instance + ValueTask> FilterAsync( + IQueryable query, + TFilters filters); + + /// + /// Applies valid paging options over instance. + /// + /// Base instance + /// Instance of paging options + /// Entity type + /// Updated instance + ValueTask> PageAsync( + IQueryable query, + PagingOptions pagingOptions); + + /// + /// Applies valid cursor paging options over instance. + /// + /// Base instance + /// Expression targeting entity property to use for cursor paging + /// Instance of paging options + /// Entity type + /// After type + /// Property type + /// Updated instance + ValueTask> PageWithCursorAsync( + IQueryable query, + Expression> cursorPropertyExpression, + CursorPagingOptions? pagingOptions); +} + +/// +/// Ooze resolver instance, contains implementations of Filtering, Sorting and Paging methods used to update +/// instances. +/// +/// Entity type +/// Filter implementation type +/// Sorter implementation type +public interface IAsyncOperationResolver +{ + /// + /// Registers passed instance to for upcoming operations + /// + /// Base instance + /// Resolver fluent instance + IAsyncOperationResolver WithQuery(IQueryable query); + + /// + /// Applies valid sorters over instance. Sorters application is based of sorter provider + /// implementation for entity type + /// + /// Sorter definitions to apply over instance + /// Updated instance + IAsyncOperationResolver Sort(IEnumerable sorters); + + /// + /// Applies valid filters over instance. Filter application is based of filter provider + /// implementation for entity type + /// + /// Filter definitions to apply over instance + /// Updated instance + IAsyncOperationResolver Filter(TFilters filters); + + /// + /// Applies valid paging options over instance. + /// + /// Instance of paging options + /// Updated instance + IAsyncOperationResolver Page(PagingOptions pagingOptions); + + /// + /// Apply and return update instance asynchronously + /// + /// Updated instance + ValueTask> ApplyAsync(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/IOozeTypedResolver.cs b/src/Ooze.Typed/IOperationResolver.cs similarity index 93% rename from src/Ooze.Typed/IOozeTypedResolver.cs rename to src/Ooze.Typed/IOperationResolver.cs index b2efbf0..0fea555 100644 --- a/src/Ooze.Typed/IOozeTypedResolver.cs +++ b/src/Ooze.Typed/IOperationResolver.cs @@ -7,7 +7,7 @@ namespace Ooze.Typed; /// Ooze resolver instance, contains implementations of Filtering, Sorting and Paging methods used to update /// instances. /// -public interface IOozeTypedResolver +public interface IOperationResolver { /// /// Applies valid sorters over instance. Sorters application is based of sorter provider @@ -69,14 +69,14 @@ IQueryable PageWithCursor( /// Entity type /// Filter implementation type /// Sorter implementation type -public interface IOozeTypedResolver +public interface IOperationResolver { /// - /// Registers passed instance to for upcoming operations + /// Registers passed instance to for upcoming operations /// /// Base instance /// Resolver fluent instance - IOozeTypedResolver WithQuery(IQueryable query); + IOperationResolver WithQuery(IQueryable query); /// /// Applies valid sorters over instance. Sorters application is based of sorter provider @@ -84,7 +84,7 @@ public interface IOozeTypedResolver /// /// Sorter definitions to apply over instance /// Updated instance - IOozeTypedResolver Sort(IEnumerable sorters); + IOperationResolver Sort(IEnumerable sorters); /// /// Applies valid filters over instance. Filter application is based of filter provider @@ -92,14 +92,14 @@ public interface IOozeTypedResolver /// /// Filter definitions to apply over instance /// Updated instance - IOozeTypedResolver Filter(TFilters filters); + IOperationResolver Filter(TFilters filters); /// /// Applies valid paging options over instance. /// /// Instance of paging options /// Updated instance - IOozeTypedResolver Page(PagingOptions pagingOptions); + IOperationResolver Page(PagingOptions pagingOptions); /// /// Applies valid paging options over instance. @@ -109,7 +109,7 @@ public interface IOozeTypedResolver /// After type /// Property type /// Updated instance - IOozeTypedResolver PageWithCursor( + IOperationResolver PageWithCursor( Expression> cursorPropertyExpression, CursorPagingOptions? pagingOptions); diff --git a/src/Ooze.Typed/OozeTypedResolver.cs b/src/Ooze.Typed/OperationResolver.cs similarity index 85% rename from src/Ooze.Typed/OozeTypedResolver.cs rename to src/Ooze.Typed/OperationResolver.cs index 441cc0b..13fe65f 100644 --- a/src/Ooze.Typed/OozeTypedResolver.cs +++ b/src/Ooze.Typed/OperationResolver.cs @@ -8,10 +8,10 @@ namespace Ooze.Typed; /// -internal class OozeTypedResolver( +internal class OperationResolver( IServiceProvider serviceProvider, - ILogger log) - : IOozeTypedResolver + ILogger log) + : IOperationResolver { public IQueryable Filter( IQueryable query, @@ -23,7 +23,7 @@ public IQueryable Filter( return query; } - var filterHandler = serviceProvider.GetRequiredService>(); + var filterHandler = serviceProvider.GetRequiredService>(); query = filterHandler.Apply(query, filters); return query; @@ -41,7 +41,7 @@ public IQueryable Sort( } - var sorterHandler = serviceProvider.GetRequiredService>(); + var sorterHandler = serviceProvider.GetRequiredService>(); query = sorterHandler.Apply(query, sorters); return query; @@ -83,22 +83,22 @@ public IQueryable PageWithCursor( } /// -internal class OozeTypedResolver( - IOozeSorterHandler sorterHandler, - IOozeFilterHandler filterHandler, +internal class OperationResolver( + ISorterHandler sorterHandler, + IFilterHandler filterHandler, IOozePagingHandler pagingHandler, - ILogger> log) - : IOozeTypedResolver + ILogger> log) + : IOperationResolver { private IQueryable _query = null!; - public IOozeTypedResolver WithQuery(IQueryable query) + public IOperationResolver WithQuery(IQueryable query) { _query = query; return this; } - public IOozeTypedResolver Sort(IEnumerable? sorters) + public IOperationResolver Sort(IEnumerable? sorters) { sorters ??= Enumerable.Empty(); if (sorters.Any() == false) @@ -112,7 +112,7 @@ public IOozeTypedResolver Sort(IEnumerable Filter(TFilters? filters) + public IOperationResolver Filter(TFilters? filters) { if (filters is null) { @@ -125,7 +125,7 @@ public IOozeTypedResolver Filter(TFilters? filters) return this; } - public IOozeTypedResolver Page(PagingOptions? pagingOptions) + public IOperationResolver Page(PagingOptions? pagingOptions) { if (pagingOptions == null) { @@ -137,7 +137,7 @@ public IOozeTypedResolver Page(PagingOptions? pagin return this; } - public IOozeTypedResolver PageWithCursor( + public IOperationResolver PageWithCursor( Expression> cursorPropertyExpression, CursorPagingOptions? pagingOptions) { diff --git a/src/Ooze.Typed/Sorters/Async/AsyncSortDefinition.cs b/src/Ooze.Typed/Sorters/Async/AsyncSortDefinition.cs new file mode 100644 index 0000000..345bfc7 --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/AsyncSortDefinition.cs @@ -0,0 +1,26 @@ +using System.Linq.Expressions; + +namespace Ooze.Typed.Sorters.Async; + +/// +/// Represents a single async sorter definition +/// +/// +/// +public class AsyncSortDefinition +{ + /// + /// Sorting expression + /// + public LambdaExpression DataExpression { get; init; } = null!; + + /// + /// Delegate which decides if the sorter should be applied + /// + public Func> ShouldRun { get; init; } = null!; + + /// + /// Delegate which provides sorting direction + /// + public Func> GetSortDirection { get; init; } = null!; +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/Async/AsyncSorterBuilder.cs b/src/Ooze.Typed/Sorters/Async/AsyncSorterBuilder.cs new file mode 100644 index 0000000..11d3d57 --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/AsyncSorterBuilder.cs @@ -0,0 +1,52 @@ +using System.Linq.Expressions; + +namespace Ooze.Typed.Sorters.Async; + +internal class AsyncSorterBuilder : IAsyncSorterBuilder +{ + private readonly IList> _sortDefinitions = + new List>(); + + public IAsyncSorterBuilder SortBy( + Expression> dataExpression, + Func sorterFunc, + Func? shouldRun = null) + { + shouldRun ??= sorters => sorterFunc(sorters) != null; + _sortDefinitions.Add(new AsyncSortDefinition + { + DataExpression = dataExpression, + ShouldRun = sorters => Task.FromResult(shouldRun(sorters)), + GetSortDirection = sorters => + { + var sortDirection = sorterFunc(sorters); + return Task.FromResult(sortDirection); + } + }); + + return this; + } + + public IAsyncSorterBuilder SortByAsync( + Expression> dataExpression, + Func> sorterFunc, + Func>? shouldRun = null) + { + shouldRun ??= async sorters => await sorterFunc(sorters) != null; + _sortDefinitions.Add(new AsyncSortDefinition + { + DataExpression = dataExpression, + ShouldRun = shouldRun, + GetSortDirection = async sorters => + { + var sortDirection = await sorterFunc(sorters); + return sortDirection; + } + }); + + return this; + } + + public IEnumerable> Build() + => _sortDefinitions; +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/Async/AsyncSorterHandler.cs b/src/Ooze.Typed/Sorters/Async/AsyncSorterHandler.cs new file mode 100644 index 0000000..ff44123 --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/AsyncSorterHandler.cs @@ -0,0 +1,81 @@ +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.Extensions.Logging; +using Ooze.Typed.Expressions; + +namespace Ooze.Typed.Sorters.Async; + +internal class AsyncSorterHandler( + IEnumerable> sortProviders, + ILogger> log) + : IAsyncSorterHandler +{ + public async ValueTask> ApplyAsync( + IQueryable query, + IEnumerable sorters) + { + log.LogDebug("Processing available sorters!"); + + if (query == null) throw new ArgumentNullException(nameof(query)); + var sortDefinitions = new List>(); + foreach (var provider in sortProviders) + { + var definitions = await provider.GetSortersAsync() + .ConfigureAwait(false); + sortDefinitions.AddRange(definitions); + } + + foreach (var sorter in sorters) + { + async Task?> GetSorterDefinition() + { + foreach (var sorterDefinition in sortDefinitions) + { + var shouldRun = await sorterDefinition.ShouldRun(sorter) + .ConfigureAwait(false); + if (shouldRun == true) + return sorterDefinition; + } + + return default; + } + + var sortDefinition = await GetSorterDefinition().ConfigureAwait(false); + if (sortDefinition is null) + continue; + + var sorterType = BasicExpressions.GetMemberExpression(sortDefinition.DataExpression.Body).Type; + var direction = await sortDefinition.GetSortDirection(sorter) + .ConfigureAwait(false); + MethodInfo? method; + + if (query.Expression.Type == typeof(IOrderedQueryable)) + { + method = direction == SortDirection.Ascending + ? CommonMethods.ThenBy + : CommonMethods.ThenByDescending; + } + else + { + method = direction == SortDirection.Ascending + ? CommonMethods.OrderBy + : CommonMethods.OrderByDescending; + } + + log.LogDebug("Applying sorter: [{@sorter}]", sortDefinition.DataExpression); + query = CreateSortedQueryable(query, method, sorterType, sortDefinition.DataExpression); + } + + return query; + } + + private static IQueryable CreateSortedQueryable( + IQueryable query, + MethodInfo method, + Type sorterType, + LambdaExpression dataExpression) + { + return (method.MakeGenericMethod(typeof(TEntity), sorterType) + .Invoke(null, [query, dataExpression]) as IQueryable)!; + } +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/Async/AsyncSorters.cs b/src/Ooze.Typed/Sorters/Async/AsyncSorters.cs new file mode 100644 index 0000000..cd3bd5e --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/AsyncSorters.cs @@ -0,0 +1,16 @@ +namespace Ooze.Typed.Sorters.Async; + +/// +/// Static class with helper methods to start building async sorter definitions for specified entity type and sorters type +/// +public static class AsyncSorters +{ + /// + /// Creates a new instance of async sorter builder for passed entity type and sorter type + /// + /// Entity type + /// Sorter type + /// Sorter builder instance for entity, sorter type combination + public static IAsyncSorterBuilder CreateFor() + => new AsyncSorterBuilder(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/Async/IAsyncSorterBuilder.cs b/src/Ooze.Typed/Sorters/Async/IAsyncSorterBuilder.cs new file mode 100644 index 0000000..4a56564 --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/IAsyncSorterBuilder.cs @@ -0,0 +1,43 @@ +using System.Linq.Expressions; + +namespace Ooze.Typed.Sorters.Async; + +/// +/// Interface defining contract for async sorter builder implementation +/// +/// Entity type +/// Sorter type +public interface IAsyncSorterBuilder +{ + /// + /// Creates a new sort definition fluently for specified entity property and sorter property + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of sorter class + /// Delegate returning bool value which denotes if sorter should be applied + /// Target type of entity property + /// Instance of builder for fluent building of multiple sorter definitions + IAsyncSorterBuilder SortBy( + Expression> dataExpression, + Func sorterFunc, + Func? shouldRun = null); + + /// + /// Creates a new async sort definition fluently for specified entity property and sorter property + /// + /// Expression targeting property of specified entity class + /// Delegate targeting property of sorter class + /// Delegate returning bool value which denotes if sorter should be applied + /// Target type of entity property + /// Instance of builder for fluent building of multiple sorter definitions + IAsyncSorterBuilder SortByAsync( + Expression> dataExpression, + Func> sorterFunc, + Func>? shouldRun = null); + + /// + /// Return collection of created sorter definitions + /// + /// Collection of sorter definitions + IEnumerable> Build(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/Async/IAsyncSorterHandler.cs b/src/Ooze.Typed/Sorters/Async/IAsyncSorterHandler.cs new file mode 100644 index 0000000..41b8cef --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/IAsyncSorterHandler.cs @@ -0,0 +1,8 @@ +namespace Ooze.Typed.Sorters.Async; + +internal interface IAsyncSorterHandler +{ + ValueTask> ApplyAsync( + IQueryable query, + IEnumerable sorters); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/Async/IAsyncSorterProvider.cs b/src/Ooze.Typed/Sorters/Async/IAsyncSorterProvider.cs new file mode 100644 index 0000000..5a17167 --- /dev/null +++ b/src/Ooze.Typed/Sorters/Async/IAsyncSorterProvider.cs @@ -0,0 +1,17 @@ +namespace Ooze.Typed.Sorters.Async; + +/// +/// Async sorter provider interface called internally by / +/// to fetch defined sorters for provided Entity type. +/// +/// Entity type +/// Sorter implementation type +public interface IAsyncSorterProvider +{ + /// + /// Method used for creation of definitions. These definitions are + /// used in sorting process. + /// + /// Collection of sort definitions + ValueTask>> GetSortersAsync(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/ISorterBuilder.cs b/src/Ooze.Typed/Sorters/ISorterBuilder.cs index d9f9de6..6fd53f8 100644 --- a/src/Ooze.Typed/Sorters/ISorterBuilder.cs +++ b/src/Ooze.Typed/Sorters/ISorterBuilder.cs @@ -26,5 +26,5 @@ ISorterBuilder SortBy( /// Return collection of created sorter definitions /// /// Collection of sorter definitions - IEnumerable> Build(); + IEnumerable> Build(); } \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/IOozeSorterHandler.cs b/src/Ooze.Typed/Sorters/ISorterHandler.cs similarity index 69% rename from src/Ooze.Typed/Sorters/IOozeSorterHandler.cs rename to src/Ooze.Typed/Sorters/ISorterHandler.cs index 2efde6f..5407bfa 100644 --- a/src/Ooze.Typed/Sorters/IOozeSorterHandler.cs +++ b/src/Ooze.Typed/Sorters/ISorterHandler.cs @@ -1,8 +1,8 @@ namespace Ooze.Typed.Sorters; -internal interface IOozeSorterHandler +internal interface ISorterHandler { IQueryable Apply( IQueryable query, IEnumerable sorters); -} +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/IOozeSorterProvider.cs b/src/Ooze.Typed/Sorters/ISorterProvider.cs similarity index 56% rename from src/Ooze.Typed/Sorters/IOozeSorterProvider.cs rename to src/Ooze.Typed/Sorters/ISorterProvider.cs index 64a8134..1dd8cbe 100644 --- a/src/Ooze.Typed/Sorters/IOozeSorterProvider.cs +++ b/src/Ooze.Typed/Sorters/ISorterProvider.cs @@ -1,17 +1,17 @@ namespace Ooze.Typed.Sorters; /// -/// Sorter provider interface called internally by / -/// to fetch defined sorters for provided Entity type. +/// Sorter provider interface called internally by / +/// to fetch defined sorters for provided Entity type. /// /// Entity type /// Sorter implementation type -public interface IOozeSorterProvider +public interface ISorterProvider { /// - /// Method used for creation of definitions. These definitions are + /// Method used for creation of definitions. These definitions are /// used in sorting process. /// /// Collection of sort definitions - IEnumerable> GetSorters(); -} + IEnumerable> GetSorters(); +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/SortDefinition.cs b/src/Ooze.Typed/Sorters/SortDefinition.cs index 5bbbe4b..bcaff9b 100644 --- a/src/Ooze.Typed/Sorters/SortDefinition.cs +++ b/src/Ooze.Typed/Sorters/SortDefinition.cs @@ -2,9 +2,25 @@ namespace Ooze.Typed.Sorters; -internal class SortDefinition : ISortDefinition +/// +/// Represents a single async sorter definition +/// +/// +/// +public class SortDefinition { + /// + /// Sorting expression + /// public LambdaExpression DataExpression { get; internal set; } = null!; + + /// + /// Delegate which decides if the sorter should be applied + /// public Func ShouldRun { get; set; } = null!; + + /// + /// Delegate which provides sorting direction + /// public Func GetSortDirection { get; set; } = null!; -} +} \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/SorterBuilder.cs b/src/Ooze.Typed/Sorters/SorterBuilder.cs index eb25fa8..0e7c9c6 100644 --- a/src/Ooze.Typed/Sorters/SorterBuilder.cs +++ b/src/Ooze.Typed/Sorters/SorterBuilder.cs @@ -28,6 +28,6 @@ public ISorterBuilder SortBy( return this; } - public IEnumerable> Build() + public IEnumerable> Build() => _sortDefinitions; } \ No newline at end of file diff --git a/src/Ooze.Typed/Sorters/OozeSorterHandler.cs b/src/Ooze.Typed/Sorters/SorterHandler.cs similarity index 87% rename from src/Ooze.Typed/Sorters/OozeSorterHandler.cs rename to src/Ooze.Typed/Sorters/SorterHandler.cs index 85c691e..afe44e4 100644 --- a/src/Ooze.Typed/Sorters/OozeSorterHandler.cs +++ b/src/Ooze.Typed/Sorters/SorterHandler.cs @@ -5,10 +5,10 @@ namespace Ooze.Typed.Sorters; -internal class OozeSorterHandler( - IEnumerable> sortProviders, - ILogger> log) - : IOozeSorterHandler +internal class SorterHandler( + IEnumerable> sortProviders, + ILogger> log) + : ISorterHandler { public IQueryable Apply( IQueryable query, @@ -18,7 +18,6 @@ public IQueryable Apply( if (query == null) throw new ArgumentNullException(nameof(query)); var sortDefinitions = sortProviders.SelectMany(provider => provider.GetSorters()) - .Cast>() .ToList(); foreach (var sorter in sorters) diff --git a/src/Ooze.Typed/Sorters/Sorters.cs b/src/Ooze.Typed/Sorters/Sorters.cs index 87bc275..b687ac6 100644 --- a/src/Ooze.Typed/Sorters/Sorters.cs +++ b/src/Ooze.Typed/Sorters/Sorters.cs @@ -13,4 +13,4 @@ public static class Sorters /// Sorter builder instance for entity, sorter type combination public static ISorterBuilder CreateFor() => new SorterBuilder(); -} +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.MySql/MySqlAsyncIntegrationTests.cs b/tests/Ooze.Typed.Tests.MySql/MySqlAsyncIntegrationTests.cs new file mode 100644 index 0000000..af7b2cf --- /dev/null +++ b/tests/Ooze.Typed.Tests.MySql/MySqlAsyncIntegrationTests.cs @@ -0,0 +1,164 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.MySql.OozeConfiguration.Async; + +namespace Ooze.Typed.Tests.MySql; + +public class MySqlAsyncIntegrationTests(MySqlFixture fixture) : IClassFixture +{ + [Fact] + public async Task DateDiffDay_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffDayFilter: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(DAY,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffMonth_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMonthFilter: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(MONTH,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffYear_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffYearFilter: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(YEAR,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffHour_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffHourFilter: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(HOUR,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffMinute_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMinuteFilter: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(MINUTE,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffSecond_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffSecondFilter: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(SECOND,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffMicrosecond_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMicrosecondFilter: new DateTime(2022, 2, 2, 20, 20, 22))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("TIMESTAMPDIFF(MICROSECOND,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + } + + [Fact] + public async Task Id_Sorter_Should_Update_Query_And_Return_Correctly_Ordered_Data() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + var defaultIds = await query.Select(x => x.Id) + .ToListAsync(); + + query = await resolver.SortAsync(query, + new[] { new PostSorters(Id: SortDirection.Descending) }); + var sortedIds = await query.Select(x => x.Id) + .ToListAsync(); + + Assert.False(defaultIds.SequenceEqual(sortedIds)); + Assert.False(defaultIds.Except(sortedIds).Any()); + Assert.Equal(100, defaultIds.Intersect(sortedIds).Count()); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("ORDER BY", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.MySql/MySqlFixture.cs b/tests/Ooze.Typed.Tests.MySql/MySqlFixture.cs index e0a2d02..d39a724 100644 --- a/tests/Ooze.Typed.Tests.MySql/MySqlFixture.cs +++ b/tests/Ooze.Typed.Tests.MySql/MySqlFixture.cs @@ -1,7 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Ooze.Typed.Extensions; -using Ooze.Typed.Tests.MySql.OozeConfiguration; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; using Testcontainers.MariaDb; @@ -14,21 +13,22 @@ public class MySqlFixture : IAsyncLifetime .WithCleanUp(true) .Build(); - private static IServiceCollection CreateServiceCollection() + private static IServiceCollection CreateServiceCollection(bool enableAsyncSupport = false) { var services = new ServiceCollection().AddLogging(); - services.AddOozeTyped() - .Add() - .Add(); + var oozeBuilder = services.AddOozeTyped(); + if (enableAsyncSupport == true) + oozeBuilder.EnableAsyncResolvers(); + oozeBuilder.Add(); return services; } - public readonly IServiceProvider ServiceProvider = new DefaultServiceProviderFactory( + public IServiceProvider CreateServiceProvider(bool enableAsyncSupport = false) => new DefaultServiceProviderFactory( new ServiceProviderOptions { ValidateScopes = false - }).CreateServiceProvider(CreateServiceCollection()); + }).CreateServiceProvider(CreateServiceCollection(enableAsyncSupport)); public MySqlContext CreateContext() { diff --git a/tests/Ooze.Typed.Tests.MySql/MySqlIntegrationTests.cs b/tests/Ooze.Typed.Tests.MySql/MySqlIntegrationTests.cs index 2695d47..fdaba9a 100644 --- a/tests/Ooze.Typed.Tests.MySql/MySqlIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests.MySql/MySqlIntegrationTests.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.MySql.OozeConfiguration; namespace Ooze.Typed.Tests.MySql; @@ -11,7 +12,7 @@ public async Task DateDiffDay_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffDayFilter: new DateTime(2022, 5, 20))); @@ -29,7 +30,7 @@ public async Task DateDiffMonth_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffMonthFilter: new DateTime(2022, 5, 20))); @@ -47,7 +48,7 @@ public async Task DateDiffYear_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffYearFilter: new DateTime(2022, 5, 20))); @@ -65,7 +66,7 @@ public async Task DateDiffHour_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffHourFilter: new DateTime(2022, 5, 20))); @@ -83,7 +84,7 @@ public async Task DateDiffMinute_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffMinuteFilter: new DateTime(2022, 5, 20))); @@ -101,7 +102,7 @@ public async Task DateDiffSecond_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffSecondFilter: new DateTime(2022, 5, 20))); @@ -119,7 +120,7 @@ public async Task DateDiffMicrosecond_Should_Update_Query_And_Return_Correct_Que { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffMicrosecondFilter: new DateTime(2022, 2, 2, 20, 20, 22))); @@ -134,7 +135,7 @@ public async Task Id_Sorter_Should_Update_Query_And_Return_Correctly_Ordered_Dat { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); var defaultIds = await query.Select(x => x.Id) .ToListAsync(); diff --git a/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/Async/PostAsyncFiltersProvider.cs b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/Async/PostAsyncFiltersProvider.cs new file mode 100644 index 0000000..259b5db --- /dev/null +++ b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/Async/PostAsyncFiltersProvider.cs @@ -0,0 +1,19 @@ +using Ooze.Typed.EntityFrameworkCore.MySql.Extensions; +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.MySql.OozeConfiguration.Async; + +public class PostAsyncFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .IsDateDiffDay(post => post.Date, filter => filter.DateDiffDayFilter, DateDiffOperation.Equal) + .IsDateDiffMonth(post => post.Date, filter => filter.DateDiffMonthFilter, DateDiffOperation.Equal) + .IsDateDiffYear(post => post.Date, filter => filter.DateDiffYearFilter, DateDiffOperation.Equal) + .IsDateDiffHour(post => post.Date, filter => filter.DateDiffHourFilter, DateDiffOperation.Equal) + .IsDateDiffMinute(post => post.Date, filter => filter.DateDiffMinuteFilter, DateDiffOperation.Equal) + .IsDateDiffSecond(post => post.Date, filter => filter.DateDiffSecondFilter, DateDiffOperation.Equal) + .IsDateDiffMicrosecond(post => post.Date, filter => filter.DateDiffMicrosecondFilter, + DateDiffOperation.Equal) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/Async/PostAsyncSortersProvider.cs b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/Async/PostAsyncSortersProvider.cs new file mode 100644 index 0000000..52f7713 --- /dev/null +++ b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/Async/PostAsyncSortersProvider.cs @@ -0,0 +1,11 @@ +using Ooze.Typed.Sorters.Async; + +namespace Ooze.Typed.Tests.MySql.OozeConfiguration.Async; + +public class PostAsyncSortersProvider : IAsyncSorterProvider +{ + public ValueTask>> GetSortersAsync() + => ValueTask.FromResult(AsyncSorters.CreateFor() + .SortBy(post => post.Id, sort => sort.Id) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostFiltersProvider.cs b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostFiltersProvider.cs index 4212c0b..1ab553e 100644 --- a/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostFiltersProvider.cs @@ -3,9 +3,9 @@ namespace Ooze.Typed.Tests.MySql.OozeConfiguration; -public class PostFiltersProvider : IOozeFilterProvider +public class PostFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .IsDateDiffDay(post => post.Date, filter => filter.DateDiffDayFilter, DateDiffOperation.Equal) .IsDateDiffMonth(post => post.Date, filter => filter.DateDiffMonthFilter, DateDiffOperation.Equal) @@ -13,6 +13,7 @@ public IEnumerable> GetFilters() .IsDateDiffHour(post => post.Date, filter => filter.DateDiffHourFilter, DateDiffOperation.Equal) .IsDateDiffMinute(post => post.Date, filter => filter.DateDiffMinuteFilter, DateDiffOperation.Equal) .IsDateDiffSecond(post => post.Date, filter => filter.DateDiffSecondFilter, DateDiffOperation.Equal) - .IsDateDiffMicrosecond(post => post.Date, filter => filter.DateDiffMicrosecondFilter, DateDiffOperation.Equal) + .IsDateDiffMicrosecond(post => post.Date, filter => filter.DateDiffMicrosecondFilter, + DateDiffOperation.Equal) .Build(); } \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostSortersProvider.cs b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostSortersProvider.cs index 572e7b2..400e9a1 100644 --- a/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostSortersProvider.cs +++ b/tests/Ooze.Typed.Tests.MySql/OozeConfiguration/PostSortersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.MySql.OozeConfiguration; -public class PostSortersProvider : IOozeSorterProvider +public class PostSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() => Sorters.Sorters.CreateFor() .SortBy(post => post.Id, sort => sort.Id) .Build(); diff --git a/tests/Ooze.Typed.Tests.Npgsql/NpgsqlAsyncIntegrationTests.cs b/tests/Ooze.Typed.Tests.Npgsql/NpgsqlAsyncIntegrationTests.cs new file mode 100644 index 0000000..3f25c4a --- /dev/null +++ b/tests/Ooze.Typed.Tests.Npgsql/NpgsqlAsyncIntegrationTests.cs @@ -0,0 +1,307 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Filters; +using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.Npgsql.OozeConfiguration.Async; + +namespace Ooze.Typed.Tests.Npgsql; + +public class NpgsqlAsyncIntegrationTests(NpgsqlFixture fixture) : IClassFixture +{ + [Theory] + [InlineData(10)] + [InlineData(1)] + [InlineData(5)] + public async Task Equal_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(PostId: postId)); + + var filteredItemsCount = await query.CountAsync(); + Assert.True(filteredItemsCount == 1); + } + + [Theory] + [InlineData(10)] + [InlineData(1)] + [InlineData(5)] + public async Task NotEqual_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NotEqualPostId: postId)); + + var containsPostId = await query.AnyAsync(post => post.Id == postId); + Assert.True(containsPostId == false); + } + + [Theory] + [InlineData(1)] + [InlineData(50)] + [InlineData(100)] + public async Task GreaterThan_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(GreaterThanPostId: postId)); + + var filteredItemsCount = await query.CountAsync(); + var expectedCount = NpgsqlContext.TotalRecords - postId; + Assert.True(filteredItemsCount == expectedCount); + } + + [Theory] + [InlineData(1)] + [InlineData(50)] + [InlineData(100)] + public async Task LessThan_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(LessThanPostId: postId)); + + var filteredItemsCount = await query.CountAsync(); + var expectedCount = postId - 1; + Assert.True(filteredItemsCount == expectedCount); + } + + [Fact] + public async Task In_Should_Update_Query_And_Return_Correct_Query() + { + var postIds = new long[] { 1, 10, 100 }; + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdIn: postIds)); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var containsAll = materializedIds.SequenceEqual(postIds); + + Assert.True(containsAll == true); + } + + [Fact] + public async Task NotIn_Should_Update_Query_And_Return_Correct_Query() + { + var postIds = new long[] { -1, 1000, -100 }; + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdIn: postIds)); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var containsAny = materializedIds.Intersect(postIds).Any(); + + Assert.True(containsAny == false); + } + + [Theory] + [InlineData(-200, 1000)] + [InlineData(1, 100)] + [InlineData(50, 50)] + [InlineData(30, 101)] + public async Task Range_Should_Update_Query_And_Return_Correct_Query( + long from, + long to) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdRange: new RangeFilter + { + From = from, + To = to + })); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var allIdsValid = materializedIds.All(x => x >= from && x <= to); + + Assert.True(allIdsValid == true); + } + + [Theory] + [InlineData(-200, 1000)] + [InlineData(1, 100)] + [InlineData(50, 50)] + [InlineData(30, 101)] + public async Task OutOfRange_Should_Update_Query_And_Return_Correct_Query( + long from, + long to) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdOutOfRange: new RangeFilter + { + From = from, + To = to + })); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var allIdsValid = materializedIds.All(x => x < from || x > to); + + Assert.True(allIdsValid == true); + } + + [Theory] + [InlineData("1_Sample")] + [InlineData("12_Sample")] + [InlineData("50_Sample")] + public async Task StartsWith_Should_Update_Query_And_Return_Correct_Query(string prefix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameStartsWith: prefix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.StartsWith(prefix)); + + Assert.True(allEntitiesValid == true); + } + + [Theory] + [InlineData("dlkjsad")] + [InlineData("3213")] + [InlineData("$!#")] + public async Task DoesntStartWith_Should_Update_Query_And_Return_Correct_Query(string prefix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameDoesntWith: prefix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.StartsWith(prefix) == false); + + Assert.True(allEntitiesValid == true); + } + + [Theory] + [InlineData("post_1")] + [InlineData("post_12")] + [InlineData("post_50")] + public async Task EndsWith_Should_Update_Query_And_Return_Correct_Query(string suffix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameEndsWith: suffix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.EndsWith(suffix)); + + Assert.True(allEntitiesValid == true); + } + + [Theory] + [InlineData("dlkjsad")] + [InlineData("3213")] + [InlineData("$!#")] + public async Task DoesntEndWith_Should_Update_Query_And_Return_Correct_Query(string suffix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameDoesntEndWith: suffix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.EndsWith(suffix) == false); + + Assert.True(allEntitiesValid == true); + } + + [Fact] + public async Task InsensitiveLike_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameLikeFilter: "%Sample%")); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("ILIKE", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task SoundexEqual_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameSoundexEqual: "%Sample%")); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("soundex", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task Id_Sorter_Should_Update_Query_And_Return_Correctly_Ordered_Data() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + var defaultIds = await query.Select(x => x.Id) + .ToListAsync(); + + query = await resolver.SortAsync(query, + new[] { new PostSorters(Id: SortDirection.Descending) }); + var sortedIds = await query.Select(x => x.Id) + .ToListAsync(); + + Assert.False(defaultIds.SequenceEqual(sortedIds)); + Assert.False(defaultIds.Except(sortedIds).Any()); + Assert.Equal(100, defaultIds.Intersect(sortedIds).Count()); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("ORDER BY", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.Npgsql/NpgsqlFixture.cs b/tests/Ooze.Typed.Tests.Npgsql/NpgsqlFixture.cs index c51b175..fbf9655 100644 --- a/tests/Ooze.Typed.Tests.Npgsql/NpgsqlFixture.cs +++ b/tests/Ooze.Typed.Tests.Npgsql/NpgsqlFixture.cs @@ -14,21 +14,22 @@ public class NpgsqlFixture : IAsyncLifetime .WithCleanUp(true) .Build(); - private static IServiceCollection CreateServiceCollection() + private static IServiceCollection CreateServiceCollection(bool enableAsyncSupport = false) { var services = new ServiceCollection().AddLogging(); - services.AddOozeTyped() - .Add() - .Add(); + var oozeBuilder = services.AddOozeTyped(); + if (enableAsyncSupport == true) + oozeBuilder.EnableAsyncResolvers(); + oozeBuilder.Add(); return services; } - public readonly IServiceProvider ServiceProvider = new DefaultServiceProviderFactory( + public IServiceProvider CreateServiceProvider(bool enableAsyncSupport = false) => new DefaultServiceProviderFactory( new ServiceProviderOptions { ValidateScopes = false - }).CreateServiceProvider(CreateServiceCollection()); + }).CreateServiceProvider(CreateServiceCollection(enableAsyncSupport)); public NpgsqlContext CreateContext() { diff --git a/tests/Ooze.Typed.Tests.Npgsql/NpgsqlIntegrationTests.cs b/tests/Ooze.Typed.Tests.Npgsql/NpgsqlIntegrationTests.cs index ca72537..4bbc8d5 100644 --- a/tests/Ooze.Typed.Tests.Npgsql/NpgsqlIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests.Npgsql/NpgsqlIntegrationTests.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Ooze.Typed.Filters; using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.Npgsql.OozeConfiguration; namespace Ooze.Typed.Tests.Npgsql; @@ -15,7 +16,8 @@ public async Task Equal_Should_Update_Query_And_Return_Correct_Query(int postId) { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(PostId: postId)); @@ -31,7 +33,8 @@ public async Task NotEqual_Should_Update_Query_And_Return_Correct_Query(int post { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NotEqualPostId: postId)); @@ -47,7 +50,8 @@ public async Task GreaterThan_Should_Update_Query_And_Return_Correct_Query(int p { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(GreaterThanPostId: postId)); @@ -64,7 +68,8 @@ public async Task LessThan_Should_Update_Query_And_Return_Correct_Query(int post { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(LessThanPostId: postId)); @@ -79,7 +84,8 @@ public async Task In_Should_Update_Query_And_Return_Correct_Query() var postIds = new long[] { 1, 10, 100 }; await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdIn: postIds)); @@ -96,7 +102,8 @@ public async Task NotIn_Should_Update_Query_And_Return_Correct_Query() var postIds = new long[] { -1, 1000, -100 }; await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdIn: postIds)); @@ -118,7 +125,8 @@ public async Task Range_Should_Update_Query_And_Return_Correct_Query( { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdRange: new RangeFilter { @@ -144,7 +152,8 @@ public async Task OutOfRange_Should_Update_Query_And_Return_Correct_Query( { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdOutOfRange: new RangeFilter { @@ -167,7 +176,8 @@ public async Task StartsWith_Should_Update_Query_And_Return_Correct_Query(string { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameStartsWith: prefix)); @@ -185,7 +195,8 @@ public async Task DoesntStartWith_Should_Update_Query_And_Return_Correct_Query(s { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameDoesntWith: prefix)); @@ -203,7 +214,8 @@ public async Task EndsWith_Should_Update_Query_And_Return_Correct_Query(string s { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameEndsWith: suffix)); @@ -221,7 +233,8 @@ public async Task DoesntEndWith_Should_Update_Query_And_Return_Correct_Query(str { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameDoesntEndWith: suffix)); @@ -236,7 +249,8 @@ public async Task InsensitiveLike_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameLikeFilter: "%Sample%")); @@ -253,7 +267,8 @@ public async Task SoundexEqual_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameSoundexEqual: "%Sample%")); @@ -264,26 +279,27 @@ public async Task SoundexEqual_Should_Update_Query_And_Return_Correct_Query() var hasFilteredItems = await query.AnyAsync(); Assert.True(hasFilteredItems == false); } - + [Fact] public async Task Id_Sorter_Should_Update_Query_And_Return_Correctly_Ordered_Data() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider() + .GetRequiredService(); IQueryable query = context.Set(); var defaultIds = await query.Select(x => x.Id) .ToListAsync(); - + query = resolver.Sort(query, new[] { new PostSorters(Id: SortDirection.Descending) }); var sortedIds = await query.Select(x => x.Id) - .ToListAsync(); - + .ToListAsync(); + Assert.True(defaultIds.SequenceEqual(sortedIds) == false); Assert.True(defaultIds.Except(sortedIds).Any() == false); Assert.True(defaultIds.Intersect(sortedIds).Count() == 100); - + var sql = query.ToQueryString(); var sqlContainsCall = sql.Contains("ORDER BY", StringComparison.InvariantCultureIgnoreCase); Assert.True(sqlContainsCall); diff --git a/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/Async/PostAsyncFiltersProvider.cs b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/Async/PostAsyncFiltersProvider.cs new file mode 100644 index 0000000..88f8c54 --- /dev/null +++ b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/Async/PostAsyncFiltersProvider.cs @@ -0,0 +1,25 @@ +using Ooze.Typed.EntityFrameworkCore.Npgsql.Extensions; +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Npgsql.OozeConfiguration.Async; + +public class PostAsyncFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .Equal(post => post.Id, filter => filter.PostId) + .NotEqual(post => post.Id, filter => filter.NotEqualPostId) + .GreaterThan(post => post.Id, filter => filter.GreaterThanPostId) + .LessThan(post => post.Id, filter => filter.LessThanPostId) + .In(post => post.Id, filter => filter.IdIn) + .NotIn(post => post.Id, filter => filter.IdNotIn) + .Range(post => post.Id, filter => filter.IdRange) + .OutOfRange(post => post.Id, filter => filter.IdOutOfRange) + .StartsWith(post => post.Name, filter => filter.NameStartsWith) + .DoesntStartWith(post => post.Name, filter => filter.NameDoesntWith) + .EndsWith(post => post.Name, filter => filter.NameEndsWith) + .DoesntEndWith(post => post.Name, filter => filter.NameDoesntEndWith) + .InsensitiveLike(post => post.Name, filter => filter.NameLikeFilter) + .SoundexEqual(post => post.Name, filter => filter.NameSoundexEqual) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/Async/PostAsyncSortersProvider.cs b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/Async/PostAsyncSortersProvider.cs new file mode 100644 index 0000000..7e218df --- /dev/null +++ b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/Async/PostAsyncSortersProvider.cs @@ -0,0 +1,11 @@ +using Ooze.Typed.Sorters.Async; + +namespace Ooze.Typed.Tests.Npgsql.OozeConfiguration.Async; + +public class PostAsyncSortersProvider : IAsyncSorterProvider +{ + public ValueTask>> GetSortersAsync() + => ValueTask.FromResult(AsyncSorters.CreateFor() + .SortBy(post => post.Id, sort => sort.Id) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostFiltersProvider.cs b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostFiltersProvider.cs index 88ae9c9..143ef69 100644 --- a/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostFiltersProvider.cs @@ -3,9 +3,9 @@ namespace Ooze.Typed.Tests.Npgsql.OozeConfiguration; -public class PostFiltersProvider : IOozeFilterProvider +public class PostFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .Equal(post => post.Id, filter => filter.PostId) .NotEqual(post => post.Id, filter => filter.NotEqualPostId) diff --git a/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostSortersProvider.cs b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostSortersProvider.cs index 2c42ee7..9119e04 100644 --- a/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostSortersProvider.cs +++ b/tests/Ooze.Typed.Tests.Npgsql/OozeConfiguration/PostSortersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Npgsql.OozeConfiguration; -public class PostSortersProvider : IOozeSorterProvider +public class PostSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() => Sorters.Sorters.CreateFor() .SortBy(post => post.Id, sort => sort.Id) .Build(); diff --git a/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/Async/PostAsyncFiltersProvider.cs b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/Async/PostAsyncFiltersProvider.cs new file mode 100644 index 0000000..2b844e4 --- /dev/null +++ b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/Async/PostAsyncFiltersProvider.cs @@ -0,0 +1,36 @@ +using Ooze.Typed.EntityFrameworkCore.SqlServer.Extensions; +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.SqlServer.OozeConfiguration.Async; + +public class PostAsyncFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .Equal(post => post.Id, filter => filter.PostId) + .NotEqual(post => post.Id, filter => filter.NotEqualPostId) + .GreaterThan(post => post.Id, filter => filter.GreaterThanPostId) + .LessThan(post => post.Id, filter => filter.LessThanPostId) + .In(post => post.Id, filter => filter.IdIn) + .NotIn(post => post.Id, filter => filter.IdNotIn) + .Range(post => post.Id, filter => filter.IdRange) + .OutOfRange(post => post.Id, filter => filter.IdOutOfRange) + .StartsWith(post => post.Name, filter => filter.NameStartsWith) + .DoesntStartWith(post => post.Name, filter => filter.NameDoesntWith) + .EndsWith(post => post.Name, filter => filter.NameEndsWith) + .DoesntEndWith(post => post.Name, filter => filter.NameDoesntEndWith) + .IsDate(post => post.Name, filter => filter.IsNameDate) + .IsNumeric(post => post.Name, filter => filter.IsIdNumeric) + .IsDateDiffDay(post => post.Date, filter => filter.DateDiffDay, DateDiffOperation.NotEqual) + .IsDateDiffDay(post => post.Date, filter => filter.DateDiffDayEqual, DateDiffOperation.Equal) + .IsDateDiffMonth(post => post.Date, filter => filter.DateDiffMonth, DateDiffOperation.NotEqual) + .IsDateDiffYear(post => post.Date, filter => filter.DateDiffYear, DateDiffOperation.NotEqual) + .IsDateDiffWeek(post => post.Date, filter => filter.DateDiffWeek, DateDiffOperation.NotEqual) + .IsDateDiffHour(post => post.Date, filter => filter.DateDiffHour, DateDiffOperation.NotEqual) + .IsDateDiffMinute(post => post.Date, filter => filter.DateDiffMinute, DateDiffOperation.NotEqual) + .IsDateDiffSecond(post => post.Date, filter => filter.DateDiffSecond, DateDiffOperation.NotEqual) + .IsDateDiffMillisecond(post => post.Date, filter => filter.DateDiffMillisecond, DateDiffOperation.NotEqual) + .IsDateDiffMicrosecond(post => post.Date, filter => filter.DateDiffMicrosecond, DateDiffOperation.NotEqual) + .IsDateDiffNanosecond(post => post.Date, filter => filter.DateDiffNanosecond, DateDiffOperation.NotEqual) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/Async/PostAsyncSortersProvider.cs b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/Async/PostAsyncSortersProvider.cs new file mode 100644 index 0000000..f7a957a --- /dev/null +++ b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/Async/PostAsyncSortersProvider.cs @@ -0,0 +1,11 @@ +using Ooze.Typed.Sorters.Async; + +namespace Ooze.Typed.Tests.SqlServer.OozeConfiguration.Async; + +public class PostAsyncSortersProvider : IAsyncSorterProvider +{ + public ValueTask>> GetSortersAsync() + => ValueTask.FromResult(AsyncSorters.CreateFor() + .SortBy(post => post.Id, sort => sort.Id) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostFiltersProvider.cs b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostFiltersProvider.cs index 4dd4fab..2fe974d 100644 --- a/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostFiltersProvider.cs @@ -3,9 +3,9 @@ namespace Ooze.Typed.Tests.SqlServer.OozeConfiguration; -public class PostFiltersProvider : IOozeFilterProvider +public class PostFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .Equal(post => post.Id, filter => filter.PostId) .NotEqual(post => post.Id, filter => filter.NotEqualPostId) diff --git a/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostSortersProvider.cs b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostSortersProvider.cs index 8160430..e5b860e 100644 --- a/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostSortersProvider.cs +++ b/tests/Ooze.Typed.Tests.SqlServer/OozeConfiguration/PostSortersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.SqlServer.OozeConfiguration; -public class PostSortersProvider : IOozeSorterProvider +public class PostSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() => Sorters.Sorters.CreateFor() .SortBy(post => post.Id, sort => sort.Id) .Build(); diff --git a/tests/Ooze.Typed.Tests.SqlServer/SqlServerAsyncIntegrationTests.cs b/tests/Ooze.Typed.Tests.SqlServer/SqlServerAsyncIntegrationTests.cs new file mode 100644 index 0000000..f948362 --- /dev/null +++ b/tests/Ooze.Typed.Tests.SqlServer/SqlServerAsyncIntegrationTests.cs @@ -0,0 +1,500 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Filters; +using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.SqlServer.OozeConfiguration.Async; + +namespace Ooze.Typed.Tests.SqlServer; + +public class SqlServerAsyncIntegrationTests(SqlServerFixture fixture) : IClassFixture +{ + [Theory] + [InlineData(10)] + [InlineData(1)] + [InlineData(5)] + public async Task Equal_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(PostId: postId)); + + var filteredItemsCount = await query.CountAsync(); + Assert.True(filteredItemsCount == 1); + } + + [Theory] + [InlineData(10)] + [InlineData(1)] + [InlineData(5)] + public async Task NotEqual_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NotEqualPostId: postId)); + + var containsPostId = await query.AnyAsync(post => post.Id == postId); + Assert.True(containsPostId == false); + } + + [Theory] + [InlineData(1)] + [InlineData(50)] + [InlineData(100)] + public async Task GreaterThan_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(GreaterThanPostId: postId)); + + var filteredItemsCount = await query.CountAsync(); + var expectedCount = SqlServerContext.TotalRecords - postId; + Assert.True(filteredItemsCount == expectedCount); + } + + [Theory] + [InlineData(1)] + [InlineData(50)] + [InlineData(100)] + public async Task LessThan_Should_Update_Query_And_Return_Correct_Query(int postId) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(LessThanPostId: postId)); + + var filteredItemsCount = await query.CountAsync(); + var expectedCount = postId - 1; + Assert.True(filteredItemsCount == expectedCount); + } + + [Fact] + public async Task In_Should_Update_Query_And_Return_Correct_Query() + { + var postIds = new long[] { 1, 10, 100 }; + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdIn: postIds)); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var containsAll = materializedIds.SequenceEqual(postIds); + + Assert.True(containsAll == true); + } + + [Fact] + public async Task NotIn_Should_Update_Query_And_Return_Correct_Query() + { + var postIds = new long[] { -1, 1000, -100 }; + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdIn: postIds)); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var containsAny = materializedIds.Intersect(postIds).Any(); + + Assert.True(containsAny == false); + } + + [Theory] + [InlineData(-200, 1000)] + [InlineData(1, 100)] + [InlineData(50, 50)] + [InlineData(30, 101)] + public async Task Range_Should_Update_Query_And_Return_Correct_Query( + long from, + long to) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdRange: new RangeFilter + { + From = from, + To = to + })); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var allIdsValid = materializedIds.All(x => x >= from && x <= to); + + Assert.True(allIdsValid == true); + } + + [Theory] + [InlineData(-200, 1000)] + [InlineData(1, 100)] + [InlineData(50, 50)] + [InlineData(30, 101)] + public async Task OutOfRange_Should_Update_Query_And_Return_Correct_Query( + long from, + long to) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IdOutOfRange: new RangeFilter + { + From = from, + To = to + })); + + var materializedIds = await query.Select(x => x.Id) + .ToListAsync(); + var allIdsValid = materializedIds.All(x => x < from || x > to); + + Assert.True(allIdsValid == true); + } + + [Theory] + [InlineData("1_Sample")] + [InlineData("12_Sample")] + [InlineData("50_Sample")] + public async Task StartsWith_Should_Update_Query_And_Return_Correct_Query(string prefix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameStartsWith: prefix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.StartsWith(prefix)); + + Assert.True(allEntitiesValid == true); + } + + [Theory] + [InlineData("dlkjsad")] + [InlineData("3213")] + [InlineData("$!#")] + public async Task DoesntStartWith_Should_Update_Query_And_Return_Correct_Query(string prefix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameDoesntWith: prefix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.StartsWith(prefix) == false); + + Assert.True(allEntitiesValid == true); + } + + [Theory] + [InlineData("post_1")] + [InlineData("post_12")] + [InlineData("post_50")] + public async Task EndsWith_Should_Update_Query_And_Return_Correct_Query(string suffix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameEndsWith: suffix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.EndsWith(suffix)); + + Assert.True(allEntitiesValid == true); + } + + [Theory] + [InlineData("dlkjsad")] + [InlineData("3213")] + [InlineData("$!#")] + public async Task DoesntEndWith_Should_Update_Query_And_Return_Correct_Query(string suffix) + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(NameDoesntEndWith: suffix)); + + var materialized = await query.ToListAsync(); + var allEntitiesValid = materialized.All(x => x.Name.EndsWith(suffix) == false); + + Assert.True(allEntitiesValid == true); + } + + [Fact] + public async Task IsDate_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IsNameDate: true)); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("ISDATE", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task IsNumeric_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, new PostFilters(IsIdNumeric: true)); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("ISNUMERIC", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffDay_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffDay: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(day,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffMonth_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMonth: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(month,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffYear_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffYear: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(year,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffWeek_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffWeek: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(week,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffHour_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffHour: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(hour,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffMinute_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMinute: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(minute,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffSecond_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffSecond: new DateTime(2022, 5, 20))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(second,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == true); + } + + [Fact] + public async Task DateDiffMillisecond_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMillisecond: new DateTime(2022, 2, 2, 20, 20, 22), + DateDiffDayEqual: new DateTime(2022, 2, 2))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(millisecond,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffMicrosecond_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffMicrosecond: new DateTime(2022, 2, 2, 20, 20, 22), + DateDiffDayEqual: new DateTime(2022, 2, 2, 20, 20, 22))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(microsecond,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task DateDiffNanosecond_Should_Update_Query_And_Return_Correct_Query() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + query = await resolver.FilterAsync(query, + new PostFilters(DateDiffDayEqual: new DateTime(2022, 2, 2, 20, 20, 22), + DateDiffNanosecond: new DateTime(2022, 2, 2, 20, 20, 22))); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("DATEDIFF(nanosecond,", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + + var hasFilteredItems = await query.AnyAsync(); + Assert.True(hasFilteredItems == false); + } + + [Fact] + public async Task Id_Sorter_Should_Update_Query_And_Return_Correctly_Ordered_Data() + { + await using var context = fixture.CreateContext(); + + var resolver = fixture.CreateServiceProvider(true) + .GetRequiredService(); + IQueryable query = context.Set(); + var defaultIds = await query.Select(x => x.Id) + .ToListAsync(); + + query = await resolver.SortAsync(query, + new[] { new PostSorters(Id: SortDirection.Descending) }); + var sortedIds = await query.Select(x => x.Id) + .ToListAsync(); + + Assert.False(defaultIds.SequenceEqual(sortedIds)); + Assert.False(defaultIds.Except(sortedIds).Any()); + Assert.Equal(100, defaultIds.Intersect(sortedIds).Count()); + + var sql = query.ToQueryString(); + var sqlContainsCall = sql.Contains("ORDER BY", StringComparison.InvariantCultureIgnoreCase); + Assert.True(sqlContainsCall); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests.SqlServer/SqlServerFixture.cs b/tests/Ooze.Typed.Tests.SqlServer/SqlServerFixture.cs index efc8056..be72022 100644 --- a/tests/Ooze.Typed.Tests.SqlServer/SqlServerFixture.cs +++ b/tests/Ooze.Typed.Tests.SqlServer/SqlServerFixture.cs @@ -14,21 +14,22 @@ public class SqlServerFixture : IAsyncLifetime .WithCleanUp(true) .Build(); - private static IServiceCollection CreateServiceCollection() + private static IServiceCollection CreateServiceCollection(bool enableAsyncSupport = false) { var services = new ServiceCollection().AddLogging(); - services.AddOozeTyped() - .Add() - .Add(); + var oozeBuilder = services.AddOozeTyped(); + if (enableAsyncSupport == true) + oozeBuilder.EnableAsyncResolvers(); + oozeBuilder.Add(); return services; } - public readonly IServiceProvider ServiceProvider = new DefaultServiceProviderFactory( + public IServiceProvider CreateServiceProvider(bool enableAsyncSupport = false) => new DefaultServiceProviderFactory( new ServiceProviderOptions { ValidateScopes = false - }).CreateServiceProvider(CreateServiceCollection()); + }).CreateServiceProvider(CreateServiceCollection(enableAsyncSupport)); public SqlServerContext CreateContext() { diff --git a/tests/Ooze.Typed.Tests.SqlServer/SqlServerIntegrationTests.cs b/tests/Ooze.Typed.Tests.SqlServer/SqlServerIntegrationTests.cs index 666b1f4..f4a6372 100644 --- a/tests/Ooze.Typed.Tests.SqlServer/SqlServerIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests.SqlServer/SqlServerIntegrationTests.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Ooze.Typed.Filters; using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.SqlServer.OozeConfiguration; namespace Ooze.Typed.Tests.SqlServer; @@ -15,7 +16,7 @@ public async Task Equal_Should_Update_Query_And_Return_Correct_Query(int postId) { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(PostId: postId)); @@ -31,7 +32,7 @@ public async Task NotEqual_Should_Update_Query_And_Return_Correct_Query(int post { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NotEqualPostId: postId)); @@ -47,7 +48,7 @@ public async Task GreaterThan_Should_Update_Query_And_Return_Correct_Query(int p { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(GreaterThanPostId: postId)); @@ -64,7 +65,7 @@ public async Task LessThan_Should_Update_Query_And_Return_Correct_Query(int post { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(LessThanPostId: postId)); @@ -79,7 +80,7 @@ public async Task In_Should_Update_Query_And_Return_Correct_Query() var postIds = new long[] { 1, 10, 100 }; await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdIn: postIds)); @@ -96,7 +97,7 @@ public async Task NotIn_Should_Update_Query_And_Return_Correct_Query() var postIds = new long[] { -1, 1000, -100 }; await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdIn: postIds)); @@ -118,7 +119,7 @@ public async Task Range_Should_Update_Query_And_Return_Correct_Query( { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdRange: new RangeFilter { @@ -144,7 +145,7 @@ public async Task OutOfRange_Should_Update_Query_And_Return_Correct_Query( { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IdOutOfRange: new RangeFilter { @@ -167,7 +168,7 @@ public async Task StartsWith_Should_Update_Query_And_Return_Correct_Query(string { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameStartsWith: prefix)); @@ -185,7 +186,7 @@ public async Task DoesntStartWith_Should_Update_Query_And_Return_Correct_Query(s { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameDoesntWith: prefix)); @@ -203,7 +204,7 @@ public async Task EndsWith_Should_Update_Query_And_Return_Correct_Query(string s { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameEndsWith: suffix)); @@ -221,7 +222,7 @@ public async Task DoesntEndWith_Should_Update_Query_And_Return_Correct_Query(str { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(NameDoesntEndWith: suffix)); @@ -236,7 +237,7 @@ public async Task IsDate_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IsNameDate: true)); @@ -253,7 +254,7 @@ public async Task IsNumeric_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(IsIdNumeric: true)); @@ -270,7 +271,7 @@ public async Task DateDiffDay_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffDay: new DateTime(2022, 5, 20))); @@ -288,7 +289,7 @@ public async Task DateDiffMonth_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffMonth: new DateTime(2022, 5, 20))); @@ -306,7 +307,7 @@ public async Task DateDiffYear_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffYear: new DateTime(2022, 5, 20))); @@ -324,7 +325,7 @@ public async Task DateDiffWeek_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffWeek: new DateTime(2022, 5, 20))); @@ -342,7 +343,7 @@ public async Task DateDiffHour_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffHour: new DateTime(2022, 5, 20))); @@ -360,7 +361,7 @@ public async Task DateDiffMinute_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffMinute: new DateTime(2022, 5, 20))); @@ -378,7 +379,7 @@ public async Task DateDiffSecond_Should_Update_Query_And_Return_Correct_Query() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, new PostFilters(DateDiffSecond: new DateTime(2022, 5, 20))); @@ -396,10 +397,11 @@ public async Task DateDiffMillisecond_Should_Update_Query_And_Return_Correct_Que { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, - new PostFilters(DateDiffMillisecond: new DateTime(2022, 2, 2, 20, 20, 22), DateDiffDayEqual: new DateTime(2022, 2, 2))); + new PostFilters(DateDiffMillisecond: new DateTime(2022, 2, 2, 20, 20, 22), + DateDiffDayEqual: new DateTime(2022, 2, 2))); var sql = query.ToQueryString(); var sqlContainsCall = sql.Contains("DATEDIFF(millisecond,", StringComparison.InvariantCultureIgnoreCase); @@ -414,10 +416,11 @@ public async Task DateDiffMicrosecond_Should_Update_Query_And_Return_Correct_Que { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, - new PostFilters(DateDiffMicrosecond: new DateTime(2022, 2, 2, 20, 20, 22), DateDiffDayEqual: new DateTime(2022, 2, 2, 20, 20, 22))); + new PostFilters(DateDiffMicrosecond: new DateTime(2022, 2, 2, 20, 20, 22), + DateDiffDayEqual: new DateTime(2022, 2, 2, 20, 20, 22))); var sql = query.ToQueryString(); var sqlContainsCall = sql.Contains("DATEDIFF(microsecond,", StringComparison.InvariantCultureIgnoreCase); @@ -432,10 +435,11 @@ public async Task DateDiffNanosecond_Should_Update_Query_And_Return_Correct_Quer { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); query = resolver.Filter(query, - new PostFilters(DateDiffDayEqual: new DateTime(2022, 2, 2, 20, 20, 22), DateDiffNanosecond: new DateTime(2022, 2, 2, 20, 20, 22))); + new PostFilters(DateDiffDayEqual: new DateTime(2022, 2, 2, 20, 20, 22), + DateDiffNanosecond: new DateTime(2022, 2, 2, 20, 20, 22))); var sql = query.ToQueryString(); var sqlContainsCall = sql.Contains("DATEDIFF(nanosecond,", StringComparison.InvariantCultureIgnoreCase); @@ -444,26 +448,26 @@ public async Task DateDiffNanosecond_Should_Update_Query_And_Return_Correct_Quer var hasFilteredItems = await query.AnyAsync(); Assert.True(hasFilteredItems == false); } - + [Fact] public async Task Id_Sorter_Should_Update_Query_And_Return_Correctly_Ordered_Data() { await using var context = fixture.CreateContext(); - var resolver = fixture.ServiceProvider.GetRequiredService(); + var resolver = fixture.CreateServiceProvider().GetRequiredService(); IQueryable query = context.Set(); var defaultIds = await query.Select(x => x.Id) .ToListAsync(); - + query = resolver.Sort(query, new[] { new PostSorters(Id: SortDirection.Descending) }); var sortedIds = await query.Select(x => x.Id) - .ToListAsync(); - - Assert.True(defaultIds.SequenceEqual(sortedIds) == false); - Assert.True(defaultIds.Except(sortedIds).Any() == false); - Assert.True(defaultIds.Intersect(sortedIds).Count() == 100); - + .ToListAsync(); + + Assert.False(defaultIds.SequenceEqual(sortedIds)); + Assert.False(defaultIds.Except(sortedIds).Any()); + Assert.Equal(100, defaultIds.Intersect(sortedIds).Count()); + var sql = query.ToQueryString(); var sqlContainsCall = sql.Contains("ORDER BY", StringComparison.InvariantCultureIgnoreCase); Assert.True(sqlContainsCall); diff --git a/tests/Ooze.Typed.Tests/FilterHandlerTests.cs b/tests/Ooze.Typed.Tests/FilterHandlerTests.cs index c1e97d8..31d5685 100644 --- a/tests/Ooze.Typed.Tests/FilterHandlerTests.cs +++ b/tests/Ooze.Typed.Tests/FilterHandlerTests.cs @@ -76,20 +76,20 @@ public void Should_Call_Correct_FilterExpressionFactory() class SUT { - public IOozeFilterProvider Provider1 { get; } - public IOozeFilterProvider Provider2 { get; } + public IFilterProvider Provider1 { get; } + public IFilterProvider Provider2 { get; } public IQueryable Query { get; } - public IOozeFilterHandler Handler { get; } - private ILogger> Log { get; } + public IFilterHandler Handler { get; } + private ILogger> Log { get; } public SUT() { - Provider1 = Substitute.For>(); - Provider2 = Substitute.For>(); - Log = Substitute.For>>(); + Provider1 = Substitute.For>(); + Provider2 = Substitute.For>(); + Log = Substitute.For>>(); Query = Enumerable.Empty().AsQueryable(); - Handler = new OozeFilterHandler(new[] { Provider1, Provider2 }, Log); + Handler = new FilterHandler(new[] { Provider1, Provider2 }, Log); } } } \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterEqualIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterEqualIntegrationTests.cs new file mode 100644 index 0000000..dd8ac98 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterEqualIntegrationTests.cs @@ -0,0 +1,103 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterEqualIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public async Task Should_Correctly_Filter_Data_By_Equal_Int_Filter(int postId) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(postId, null, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 1); + Assert.True(results[0].Id == postId); + } + + [Theory] + [InlineData("1_Sample_post")] + [InlineData("5_Sample_post")] + [InlineData("10_Sample_post")] + [InlineData("100_Sample_post")] + public async Task Should_Correctly_Filter_Data_By_Equal_String_Filter(string postName) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, postName, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 1); + Assert.True(results[0].Name == postName); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Should_Correctly_Filter_Data_By_Equal_Bool_Filter(bool enabled) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, enabled, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 50); + Assert.True(results.All(x => x.Enabled == enabled) == true); + } + + [Theory] + [InlineData(2022, 1, 2)] + [InlineData(2022, 1, 3)] + [InlineData(2022, 2, 1)] + public async Task Should_Correctly_Filter_Data_By_Equal_DateTime_Filter( + int year, + int month, + int day) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var filterDate = new DateTime(year, month, day); + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, null, filterDate)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 1); + Assert.True(results[0].Date == filterDate); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterGreaterThanIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterGreaterThanIntegrationTests.cs new file mode 100644 index 0000000..437782b --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterGreaterThanIntegrationTests.cs @@ -0,0 +1,96 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterGreaterThanIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public async Task Should_Correctly_Filter_Data_By_Greater_Than_Int_Filter(int postId) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(postId, null, null, null)) + .ApplyAsync(); + var expectedCount = DatabaseContext.TotalCountOfFakes - postId; + + var results = await query.ToListAsync(); + Assert.True(results.Count == expectedCount); + Assert.True(results.All(x => x.Id > postId)); + } + + [Theory] + [InlineData("1_Sample_post")] + [InlineData("5_Sample_post")] + [InlineData("10_Sample_post")] + [InlineData("100_Sample_post")] + public async Task Should_Fail_To_Filter_Data_By_Greater_Than_String_Filter(string postName) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + await Assert.ThrowsAsync(async () => await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, postName, null, null)) + .ApplyAsync()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Should_Fail_To_Filter_Data_By_Greater_Than_Bool_Filter(bool enabled) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + await Assert.ThrowsAsync(async () => await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, enabled, null)) + .ApplyAsync()); + } + + [Theory] + [InlineData(2022, 1, 2)] + [InlineData(2022, 1, 3)] + [InlineData(2022, 2, 1)] + public async Task Should_Correctly_Filter_Data_By_Greater_Than_DateTime_Filter( + int year, + int month, + int day) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var filterDate = new DateTime(year, month, day); + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, null, filterDate)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.All(x => x.Date != filterDate)); + Assert.True(results.All(x => x.Date > filterDate)); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterInIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterInIntegrationTests.cs new file mode 100644 index 0000000..ccf94e2 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterInIntegrationTests.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterInThanIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(1, 2, 3, 4)] + [InlineData(5, 6, 7, 8)] + [InlineData(10, 11, 12, 13)] + [InlineData(100, 101, 102, 103)] + public async Task Should_Correctly_Filter_Data_By_In_Int_Filter(params int[] postIds) + { + var castedIds = postIds.Select(x => (long)x); + var validIds = castedIds.Where(x => x <= DatabaseContext.TotalCountOfFakes); + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostInFilters(castedIds, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == validIds.Count()); + Assert.True(results.All(x => validIds.Contains(x.Id))); + } + + [Theory] + [InlineData("1_Sample_post", "2_Sample_post", "3_Sample_post", "4_Sample_post")] + [InlineData("5_Sample_post", "6_Sample_post", "7_Sample_post", "8_Sample_post")] + [InlineData("10_Sample_post", "11_Sample_post", "12_Sample_post", "13_Sample_post")] + [InlineData("100_Sample_post", "101_Sample_post", "102_Sample_post", "103")] + public async Task Should_Correctly_Filter_Data_By_In_String_Filter(params string[] postNames) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostInFilters(null, postNames, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count <= postNames.Length); + Assert.True(results.All(x => postNames.Contains(x.Name))); + } + + [Theory] + [ClassData(typeof(DateData))] + public async Task Should_Correctly_Filter_Data_By_In_DateTime_Filter(params DateTime[] dates) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostInFilters(null, null, dates)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count <= dates.Length); + Assert.True(results.All(x => dates.Contains(x.Date))); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterLessThanIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterLessThanIntegrationTests.cs new file mode 100644 index 0000000..d630878 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterLessThanIntegrationTests.cs @@ -0,0 +1,96 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterLessThanIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public async Task Should_Correctly_Filter_Data_By_Less_Than_Int_Filter(int postId) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(postId, null, null, null)) + .ApplyAsync(); + var expectedCount = postId - 1; + + var results = await query.ToListAsync(); + Assert.True(results.Count == expectedCount); + Assert.True(results.All(x => x.Id < postId)); + } + + [Theory] + [InlineData("1_Sample_post")] + [InlineData("5_Sample_post")] + [InlineData("10_Sample_post")] + [InlineData("100_Sample_post")] + public async Task Should_Fail_To_Filter_Data_By_Less_Than_String_Filter(string postName) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + await Assert.ThrowsAsync(async () => await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, postName, null, null)) + .ApplyAsync()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Should_Fail_To_Filter_Data_By_Less_Than_Bool_Filter(bool enabled) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + await Assert.ThrowsAsync(async () => await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, enabled, null)) + .ApplyAsync()); + } + + [Theory] + [InlineData(2022, 1, 2)] + [InlineData(2022, 1, 3)] + [InlineData(2022, 2, 1)] + public async Task Should_Correctly_Filter_Data_By_Less_Than_DateTime_Filter( + int year, + int month, + int day) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var filterDate = new DateTime(year, month, day); + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, null, filterDate)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.All(x => x.Date != filterDate)); + Assert.True(results.All(x => x.Date < filterDate)); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterNotEqualIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterNotEqualIntegrationTests.cs new file mode 100644 index 0000000..eef4938 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterNotEqualIntegrationTests.cs @@ -0,0 +1,103 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterNotEqualIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public async Task Should_Correctly_Filter_Data_By_Not_Equal_Int_Filter(int postId) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(postId, null, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 99); + Assert.True(results.All(x => x.Id != postId)); + } + + [Theory] + [InlineData("1_Sample_post")] + [InlineData("5_Sample_post")] + [InlineData("10_Sample_post")] + [InlineData("100_Sample_post")] + public async Task Should_Correctly_Filter_Data_By_Not_Equal_String_Filter(string postName) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, postName, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 99); + Assert.True(results.All(x => x.Name != postName)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Should_Correctly_Filter_Data_By_Not_Equal_Bool_Filter(bool enabled) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, enabled, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 50); + Assert.True(results.All(x => x.Enabled != enabled)); + } + + [Theory] + [InlineData(2022, 1, 2)] + [InlineData(2022, 1, 3)] + [InlineData(2022, 2, 1)] + public async Task Should_Correctly_Filter_Data_By_Not_Equal_DateTime_Filter( + int year, + int month, + int day) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var filterDate = new DateTime(year, month, day); + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostFilters(null, null, null, filterDate)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == 99); + Assert.True(results.All(x => x.Date != filterDate)); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterNotInIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterNotInIntegrationTests.cs new file mode 100644 index 0000000..55ee376 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterNotInIntegrationTests.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterNotInThanIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(1, 2, 3, 4)] + [InlineData(5, 6, 7, 8)] + [InlineData(10, 11, 12, 13)] + [InlineData(100, 101, 102, 103)] + public async Task Should_Correctly_Filter_Data_By_Not_In_Int_Filter(params int[] postIds) + { + var castedIds = postIds.Select(x => (long)x); + var validIds = castedIds.Where(x => x <= DatabaseContext.TotalCountOfFakes); + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostInFilters(castedIds, null, null)) + .ApplyAsync(); + + + var results = await query.ToListAsync(); + Assert.True(results.All(x => validIds.Contains(x.Id) == false)); + } + + [Theory] + [InlineData("1_Sample_post", "2_Sample_post", "3_Sample_post", "4_Sample_post")] + [InlineData("5_Sample_post", "6_Sample_post", "7_Sample_post", "8_Sample_post")] + [InlineData("10_Sample_post", "11_Sample_post", "12_Sample_post", "13_Sample_post")] + [InlineData("100_Sample_post", "101_Sample_post", "102_Sample_post", "103")] + public async Task Should_Correctly_Filter_Data_By_Not_In_String_Filter(params string[] postNames) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostInFilters(null, postNames, null)) + .ApplyAsync(); + + + var results = await query.ToListAsync(); + Assert.True(results.All(x => postNames.Contains(x.Name) == false)); + } + + [Theory] + [ClassData(typeof(DateData))] + public async Task Should_Correctly_Filter_Data_By_Not_In_DateTime_Filter(params DateTime[] dates) + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostInFilters(null, null, dates)) + .ApplyAsync(); + + + var results = await query.ToListAsync(); + Assert.True(results.All(x => dates.Contains(x.Date) == false)); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterOutOfRangeIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterOutOfRangeIntegrationTests.cs new file mode 100644 index 0000000..87ced26 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterOutOfRangeIntegrationTests.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Filters; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterOutOfRangeThanIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(-10, 5)] + [InlineData(5, 20)] + [InlineData(25, 90)] + [InlineData(95, 120)] + public async Task Should_Correctly_Filter_Data_By_Out_Of_Range_Int_Filter( + int from, + int to) + { + var filter = new RangeFilter { From = from, To = to }; + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostRangeFilters(filter, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + var generatedRange = Enumerable.Range(from, to + 1).Select(x => (long)x); + Assert.Contains(results, x => generatedRange.Contains(x.Id) == false); + } + + [Theory] + [InlineData("-10", "5")] + [InlineData("postname", "endpostname")] + public async Task Should_Correctly_Filter_Data_By_Out_Of_Range_String_Filter(string from, string to) + { + var filter = new RangeFilter { From = from, To = to }; + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + await Assert.ThrowsAsync(async () => await oozeResolver.WithQuery(query) + .Filter(new PostRangeFilters(null, filter, null)) + .ApplyAsync()); + } + + [Fact] + public async Task Should_Correctly_Filter_Data_By_Out_Of_Range_DateTime_Filter() + { + var from = new DateTime(2022, 1, 1); + var to = new DateTime(2022, 2, 20); + var filter = new RangeFilter + { + From = from, + To = to + }; + + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostRangeFilters(null, null, filter)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + var diffDays = to.Subtract(from).Days; + var generatedRange = Enumerable.Range(0, diffDays + 1).Select(x => from.AddDays(x)); + Assert.Contains(results, x => generatedRange.Contains(x.Date) == false); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterRangeIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterRangeIntegrationTests.cs new file mode 100644 index 0000000..359dd6d --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseFilterRangeIntegrationTests.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Filters; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseFilterRangeThanIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Theory] + [InlineData(-10, 5)] + [InlineData(5, 20)] + [InlineData(25, 90)] + [InlineData(95, 120)] + public async Task Should_Correctly_Filter_Data_By_Range_Int_Filter( + int from, + int to) + { + var filter = new RangeFilter { From = from, To = to }; + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostRangeFilters(filter, null, null)) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.All(x => x.Id >= filter.From)); + Assert.True(results.All(x => x.Id <= filter.To)); + } + + [Theory] + [InlineData("-10", "5")] + [InlineData("postname", "endpostname")] + public async Task Should_Correctly_Filter_Data_By_Range_String_Filter(string from, string to) + { + var filter = new RangeFilter { From = from, To = to }; + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + await Assert.ThrowsAsync(async () => await oozeResolver.WithQuery(query) + .Filter(new PostRangeFilters(null, filter, null)) + .ApplyAsync()); + } + + [Fact] + public async Task Should_Correctly_Filter_Data_By_Range_DateTime_Filter() + { + var from = new DateTime(2022, 1, 1); + var to = new DateTime(2022, 2, 20); + var filter = new RangeFilter + { + From = from, + To = to + }; + + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Filter(new PostRangeFilters(null, null, filter)) + .ApplyAsync(); + + + var results = await query.ToListAsync(); + Assert.True(results.All(x => x.Date >= filter.From)); + Assert.True(results.All(x => x.Date <= filter.To)); + } +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseSorterIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseSorterIntegrationTests.cs new file mode 100644 index 0000000..948b761 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/AsyncDatabaseSorterIntegrationTests.cs @@ -0,0 +1,105 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Ooze.Typed.Sorters; +using Ooze.Typed.Tests.Integration.Setup; +using Ooze.Typed.Tests.Integration.Setup.Async; + +namespace Ooze.Typed.Tests.Integration; + +public class AsyncDatabaseSorterEqualIntegrationTests(DbFixture fixture) + : IClassFixture> +{ + [Fact] + public async Task Should_Correctly_Sort_Data_Descending_By_Single_Field() + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var sorters = new[] + { + new PostSorters(SortDirection.Descending, null, null) + }; + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Sort(sorters) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == DatabaseContext.TotalCountOfFakes); + Assert.True(results[0].Id == 100); + Assert.True(results[99].Id == 1); + } + + [Fact] + public async Task Should_Correctly_Sort_Data_Ascending_By_Single_Field() + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var sorters = new[] + { + new PostSorters(SortDirection.Ascending, null, null) + }; + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Sort(sorters) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == DatabaseContext.TotalCountOfFakes); + Assert.True(results[0].Id == 1); + Assert.True(results[99].Id == 100); + } + + [Fact] + public async Task Should_Correctly_Sort_Data_Ascending_By_Multple_Fields() + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var sorters = new[] + { + new PostSorters(SortDirection.Ascending, null, null), + new PostSorters(null, null, SortDirection.Ascending) + }; + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Sort(sorters) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == DatabaseContext.TotalCountOfFakes); + Assert.True(results[0].Id == 1); + Assert.True(results[1].Id == 2); + } + + [Fact] + public async Task Should_Not_Sort_Data_If_Sort_Not_Provided() + { + using var scope = fixture.CreateServiceProvider().CreateScope(); + var provider = scope.ServiceProvider; + + await using var context = fixture.CreateContext(); + var oozeResolver = provider.GetRequiredService>(); + + var sorters = Enumerable.Empty(); + IQueryable query = context.Posts; + query = await oozeResolver.WithQuery(query) + .Sort(sorters) + .ApplyAsync(); + + var results = await query.ToListAsync(); + Assert.True(results.Count == DatabaseContext.TotalCountOfFakes); + Assert.True(results[0].Id == 1); + Assert.True(results[99].Id == 100); + } +} diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterEqualIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterEqualIntegrationTests.cs index b2f51c7..4d289de 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterEqualIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterEqualIntegrationTests.cs @@ -18,7 +18,7 @@ public async Task Should_Correctly_Filter_Data_By_Equal_Int_Filter(int postId) var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -41,7 +41,7 @@ public async Task Should_Correctly_Filter_Data_By_Equal_String_Filter(string pos var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -62,7 +62,7 @@ public async Task Should_Correctly_Filter_Data_By_Equal_Bool_Filter(bool enabled var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -87,7 +87,7 @@ public async Task Should_Correctly_Filter_Data_By_Equal_DateTime_Filter( var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var filterDate = new DateTime(year, month, day); IQueryable query = context.Posts; diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterGreaterThanIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterGreaterThanIntegrationTests.cs index 8dc5344..3e26869 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterGreaterThanIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterGreaterThanIntegrationTests.cs @@ -18,7 +18,7 @@ public async Task Should_Correctly_Filter_Data_By_Greater_Than_Int_Filter(int po var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -42,7 +42,7 @@ public async Task Should_Fail_To_Filter_Data_By_Greater_Than_String_Filter(strin var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; Assert.Throws(() => oozeResolver.WithQuery(query) @@ -59,7 +59,7 @@ public async Task Should_Fail_To_Filter_Data_By_Greater_Than_Bool_Filter(bool en var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; Assert.Throws(() => oozeResolver.WithQuery(query) @@ -80,7 +80,7 @@ public async Task Should_Correctly_Filter_Data_By_Greater_Than_DateTime_Filter( var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var filterDate = new DateTime(year, month, day); IQueryable query = context.Posts; diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterInIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterInIntegrationTests.cs index 2ca8901..03c2eee 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterInIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterInIntegrationTests.cs @@ -21,7 +21,7 @@ public async Task Should_Correctly_Filter_Data_By_In_Int_Filter(params int[] pos var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -45,7 +45,7 @@ public async Task Should_Correctly_Filter_Data_By_In_String_Filter(params string var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -66,7 +66,7 @@ public async Task Should_Correctly_Filter_Data_By_In_DateTime_Filter(params Date var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterLessThanIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterLessThanIntegrationTests.cs index 8b13474..60a121a 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterLessThanIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterLessThanIntegrationTests.cs @@ -18,7 +18,7 @@ public async Task Should_Correctly_Filter_Data_By_Less_Than_Int_Filter(int postI var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -42,7 +42,7 @@ public async Task Should_Fail_To_Filter_Data_By_Less_Than_String_Filter(string p var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; Assert.Throws(() => oozeResolver.WithQuery(query) @@ -59,7 +59,7 @@ public async Task Should_Fail_To_Filter_Data_By_Less_Than_Bool_Filter(bool enabl var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; Assert.Throws(() => oozeResolver.WithQuery(query) @@ -80,7 +80,7 @@ public async Task Should_Correctly_Filter_Data_By_Less_Than_DateTime_Filter( var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var filterDate = new DateTime(year, month, day); IQueryable query = context.Posts; diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotEqualIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotEqualIntegrationTests.cs index aed3b8b..5b47c22 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotEqualIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotEqualIntegrationTests.cs @@ -18,7 +18,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_Equal_Int_Filter(int postI var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -41,7 +41,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_Equal_String_Filter(string var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -62,7 +62,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_Equal_Bool_Filter(bool ena var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -87,7 +87,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_Equal_DateTime_Filter( var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var filterDate = new DateTime(year, month, day); IQueryable query = context.Posts; diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotInIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotInIntegrationTests.cs index 8eec975..4fdce64 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotInIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterNotInIntegrationTests.cs @@ -20,7 +20,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_In_Int_Filter(params int[] var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -43,7 +43,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_In_String_Filter(params st var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -63,7 +63,7 @@ public async Task Should_Correctly_Filter_Data_By_Not_In_DateTime_Filter(params var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterOutOfRangeIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterOutOfRangeIntegrationTests.cs index 6f97149..ef6c284 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterOutOfRangeIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterOutOfRangeIntegrationTests.cs @@ -22,7 +22,7 @@ public async Task Should_Correctly_Filter_Data_By_Out_Of_Range_Int_Filter( var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -44,7 +44,7 @@ public async Task Should_Correctly_Filter_Data_By_Out_Of_Range_String_Filter(str var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; Assert.Throws(() => oozeResolver.WithQuery(query) @@ -67,7 +67,7 @@ public async Task Should_Correctly_Filter_Data_By_Out_Of_Range_DateTime_Filter() var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterRangeIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterRangeIntegrationTests.cs index f896529..5427f0d 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseFilterRangeIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseFilterRangeIntegrationTests.cs @@ -22,7 +22,7 @@ public async Task Should_Correctly_Filter_Data_By_Range_Int_Filter( var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) @@ -44,7 +44,7 @@ public async Task Should_Correctly_Filter_Data_By_Range_String_Filter(string fro var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; Assert.Throws(() => oozeResolver.WithQuery(query) @@ -67,7 +67,7 @@ public async Task Should_Correctly_Filter_Data_By_Range_DateTime_Filter() var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); IQueryable query = context.Posts; query = oozeResolver.WithQuery(query) diff --git a/tests/Ooze.Typed.Tests/Integration/DatabaseSorterIntegrationTests.cs b/tests/Ooze.Typed.Tests/Integration/DatabaseSorterIntegrationTests.cs index 8f96b76..fd727aa 100644 --- a/tests/Ooze.Typed.Tests/Integration/DatabaseSorterIntegrationTests.cs +++ b/tests/Ooze.Typed.Tests/Integration/DatabaseSorterIntegrationTests.cs @@ -15,7 +15,7 @@ public async Task Should_Correctly_Sort_Data_Descending_By_Single_Field() var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var sorters = new[] { @@ -39,7 +39,7 @@ public async Task Should_Correctly_Sort_Data_Ascending_By_Single_Field() var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var sorters = new[] { @@ -63,7 +63,7 @@ public async Task Should_Correctly_Sort_Data_Ascending_By_Multple_Fields() var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var sorters = new[] { @@ -88,7 +88,7 @@ public async Task Should_Not_Sort_Data_If_Sort_Not_Provided() var provider = scope.ServiceProvider; await using var context = fixture.CreateContext(); - var oozeResolver = provider.GetRequiredService>(); + var oozeResolver = provider.GetRequiredService>(); var sorters = Enumerable.Empty(); IQueryable query = context.Posts; diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostEqualFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostEqualFiltersProvider.cs new file mode 100644 index 0000000..e1520e3 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostEqualFiltersProvider.cs @@ -0,0 +1,23 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostEqualFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .Equal(post => post.Id, filter => filter.Id) + .Equal(post => post.Name, filter => filter.Name) + .Equal(post => post.Enabled, filter => filter.Enabled) + .Equal(post => post.Date, filter => filter.Date) + .AddAsync(async filters => + { + await Task.CompletedTask; + return filters.Date != null; + }, async filters => + { + await Task.CompletedTask; + return post => post.Date == filters.Date; + }) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostGreaterThanFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostGreaterThanFiltersProvider.cs new file mode 100644 index 0000000..2440a6b --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostGreaterThanFiltersProvider.cs @@ -0,0 +1,14 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostGreaterThanFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .GreaterThan(post => post.Id, filter => filter.Id) + .GreaterThan(post => post.Name, filter => filter.Name) + .GreaterThan(post => post.Enabled, filter => filter.Enabled) + .GreaterThan(post => post.Date, filter => filter.Date) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostInFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostInFiltersProvider.cs new file mode 100644 index 0000000..f5eeb62 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostInFiltersProvider.cs @@ -0,0 +1,13 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostInFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .In(post => post.Id, filter => filter.Ids) + .In(post => post.Name, filter => filter.Names) + .In(post => post.Date, filter => filter.Dates) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostLessThanFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostLessThanFiltersProvider.cs new file mode 100644 index 0000000..7eb65f6 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostLessThanFiltersProvider.cs @@ -0,0 +1,14 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostLessThanFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .LessThan(post => post.Id, filter => filter.Id) + .LessThan(post => post.Name, filter => filter.Name) + .LessThan(post => post.Enabled, filter => filter.Enabled) + .LessThan(post => post.Date, filter => filter.Date) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostNotEqualFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostNotEqualFiltersProvider.cs new file mode 100644 index 0000000..100ec55 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostNotEqualFiltersProvider.cs @@ -0,0 +1,14 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostNotEqualFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .NotEqual(post => post.Id, filter => filter.Id) + .NotEqual(post => post.Name, filter => filter.Name) + .NotEqual(post => post.Enabled, filter => filter.Enabled) + .NotEqual(post => post.Date, filter => filter.Date) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostNotInFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostNotInFiltersProvider.cs new file mode 100644 index 0000000..2c8cebd --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostNotInFiltersProvider.cs @@ -0,0 +1,13 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostNotInFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .NotIn(post => post.Id, filter => filter.Ids) + .NotIn(post => post.Name, filter => filter.Names) + .NotIn(post => post.Date, filter => filter.Dates) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostOutOfRangeFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostOutOfRangeFiltersProvider.cs new file mode 100644 index 0000000..49ab444 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostOutOfRangeFiltersProvider.cs @@ -0,0 +1,13 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostOutOfRangeFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .OutOfRange(post => post.Id, filter => filter.Ids) + .OutOfRange(post => post.Name, filter => filter.Names) + .OutOfRange(post => post.Date, filter => filter.Dates) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostRangeFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostRangeFiltersProvider.cs new file mode 100644 index 0000000..77751e3 --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostRangeFiltersProvider.cs @@ -0,0 +1,13 @@ +using Ooze.Typed.Filters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostRangeFiltersProvider : IAsyncFilterProvider +{ + public ValueTask>> GetFiltersAsync() + => ValueTask.FromResult(AsyncFilters.CreateFor() + .Range(post => post.Id, filter => filter.Ids) + .Range(post => post.Name, filter => filter.Names) + .Range(post => post.Date, filter => filter.Dates) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostSortersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostSortersProvider.cs new file mode 100644 index 0000000..261803a --- /dev/null +++ b/tests/Ooze.Typed.Tests/Integration/Setup/Async/AsyncPostSortersProvider.cs @@ -0,0 +1,12 @@ +using Ooze.Typed.Sorters.Async; + +namespace Ooze.Typed.Tests.Integration.Setup.Async; + +public class AsyncPostSortersProvider : IAsyncSorterProvider +{ + public ValueTask>> GetSortersAsync() + => ValueTask.FromResult(AsyncSorters.CreateFor() + .SortBy(post => post.Id, sort => sort.Id) + .SortBy(post => post.Enabled, sort => sort.Enabled) + .Build()); +} \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/DbFixture.cs b/tests/Ooze.Typed.Tests/Integration/Setup/DbFixture.cs index a4b0592..b3b94da 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/DbFixture.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/DbFixture.cs @@ -14,6 +14,7 @@ private static IServiceCollection CreateServiceCollection() .AddLogging(); services.AddOozeTyped() + .EnableAsyncResolvers() .Add(); return services; diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostEqualFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostEqualFiltersProvider.cs index bae7e09..8354722 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostEqualFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostEqualFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostEqualFiltersProvider : IOozeFilterProvider +public class PostEqualFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .Equal(post => post.Id, filter => filter.Id) .Equal(post => post.Name, filter => filter.Name) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostGreaterThanFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostGreaterThanFiltersProvider.cs index 3c7802a..4a97483 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostGreaterThanFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostGreaterThanFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostGreaterThanFiltersProvider : IOozeFilterProvider +public class PostGreaterThanFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .GreaterThan(post => post.Id, filter => filter.Id) .GreaterThan(post => post.Name, filter => filter.Name) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostInFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostInFiltersProvider.cs index 75e5451..b4c22ae 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostInFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostInFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostInFiltersProvider : IOozeFilterProvider +public class PostInFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .In(post => post.Id, filter => filter.Ids) .In(post => post.Name, filter => filter.Names) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostLessThanFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostLessThanFiltersProvider.cs index 9e3c3a8..683d990 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostLessThanFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostLessThanFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostLessThanFiltersProvider : IOozeFilterProvider +public class PostLessThanFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .LessThan(post => post.Id, filter => filter.Id) .LessThan(post => post.Name, filter => filter.Name) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostNotEqualFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostNotEqualFiltersProvider.cs index c6b67fd..e9c9aaa 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostNotEqualFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostNotEqualFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostNotEqualFiltersProvider : IOozeFilterProvider +public class PostNotEqualFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .NotEqual(post => post.Id, filter => filter.Id) .NotEqual(post => post.Name, filter => filter.Name) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostNotInFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostNotInFiltersProvider.cs index 4556a65..1740647 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostNotInFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostNotInFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostNotInFiltersProvider : IOozeFilterProvider +public class PostNotInFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .NotIn(post => post.Id, filter => filter.Ids) .NotIn(post => post.Name, filter => filter.Names) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostOutOfRangeFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostOutOfRangeFiltersProvider.cs index 1c50190..2a80f87 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostOutOfRangeFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostOutOfRangeFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostOutOfRangeFiltersProvider : IOozeFilterProvider +public class PostOutOfRangeFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .OutOfRange(post => post.Id, filter => filter.Ids) .OutOfRange(post => post.Name, filter => filter.Names) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostRangeFiltersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostRangeFiltersProvider.cs index 6dc2035..226f12b 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostRangeFiltersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostRangeFiltersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostRangeFiltersProvider : IOozeFilterProvider +public class PostRangeFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() => Filters.Filters.CreateFor() .Range(post => post.Id, filter => filter.Ids) .Range(post => post.Name, filter => filter.Names) diff --git a/tests/Ooze.Typed.Tests/Integration/Setup/PostSortersProvider.cs b/tests/Ooze.Typed.Tests/Integration/Setup/PostSortersProvider.cs index 39e8492..1bd39bc 100644 --- a/tests/Ooze.Typed.Tests/Integration/Setup/PostSortersProvider.cs +++ b/tests/Ooze.Typed.Tests/Integration/Setup/PostSortersProvider.cs @@ -2,9 +2,9 @@ namespace Ooze.Typed.Tests.Integration.Setup; -public class PostSortersProvider : IOozeSorterProvider +public class PostSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() => Sorters.Sorters.CreateFor() .SortBy(post => post.Id, sort => sort.Id) .SortBy(post => post.Enabled, sort => sort.Enabled) diff --git a/tests/Ooze.Typed.Tests/ServiceCollectionTests.cs b/tests/Ooze.Typed.Tests/ServiceCollectionTests.cs index 83ea5dc..2888d84 100644 --- a/tests/Ooze.Typed.Tests/ServiceCollectionTests.cs +++ b/tests/Ooze.Typed.Tests/ServiceCollectionTests.cs @@ -15,10 +15,10 @@ public void Should_Have_Registered_Default_Ooze_Interfaces() services.AddOozeTyped(); var requiredInterfaces = new (Type ContractType, Type ImplementationType)[] { - (typeof(IOozeTypedResolver), typeof(OozeTypedResolver)), - (typeof(IOozeTypedResolver<,,>), typeof(OozeTypedResolver<,,>)), - (typeof(IOozeFilterHandler<,>), typeof(OozeFilterHandler<,>)), - (typeof(IOozeSorterHandler<,>), typeof(OozeSorterHandler<,>)), + (typeof(IOperationResolver), typeof(OperationResolver)), + (typeof(IOperationResolver<,,>), typeof(OperationResolver<,,>)), + (typeof(IFilterHandler<,>), typeof(FilterHandler<,>)), + (typeof(ISorterHandler<,>), typeof(SorterHandler<,>)), (typeof(IOozePagingHandler<>), typeof(OozePagingHandler<>)) }; @@ -42,8 +42,8 @@ public void Should_Have_Custom_Provider_Implementation() var requiredInterfaces = new (Type ContractType, Type ImplementationType)[] { - (typeof(IOozeFilterProvider), typeof(BlogFiltersProvider)), - (typeof(IOozeSorterProvider), typeof(BlogSortersProvider)), + (typeof(IFilterProvider), typeof(BlogFiltersProvider)), + (typeof(ISorterProvider), typeof(BlogSortersProvider)), }; foreach (var (contractType, implementationType) in requiredInterfaces) @@ -65,8 +65,8 @@ public void Should_Have_Custom_Provider_Implementations_If_Both_Providers_Are_Im var requiredInterfaces = new (Type ContractType, Type ImplementationType)[] { - (typeof(IOozeFilterProvider), typeof(BlogFiltersAndSortersProvider)), - (typeof(IOozeSorterProvider), typeof(BlogFiltersAndSortersProvider)), + (typeof(IFilterProvider), typeof(BlogFiltersAndSortersProvider)), + (typeof(ISorterProvider), typeof(BlogFiltersAndSortersProvider)), }; foreach (var (contractType, implementationType) in requiredInterfaces) @@ -89,22 +89,22 @@ public void Should_Fail_If_Non_Provider_Type_Is_Used() } } -public class BlogFiltersProvider : IOozeFilterProvider +public class BlogFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() - => Enumerable.Empty>(); + public IEnumerable> GetFilters() + => Enumerable.Empty>(); } -public class BlogSortersProvider : IOozeSorterProvider +public class BlogSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() - => Enumerable.Empty>(); + public IEnumerable> GetSorters() + => Enumerable.Empty>(); } -public class BlogFiltersAndSortersProvider : IOozeFilterProvider, IOozeSorterProvider +public class BlogFiltersAndSortersProvider : IFilterProvider, ISorterProvider { - public IEnumerable> GetFilters() - => Enumerable.Empty>(); - public IEnumerable> GetSorters() - => Enumerable.Empty>(); + public IEnumerable> GetFilters() + => Enumerable.Empty>(); + public IEnumerable> GetSorters() + => Enumerable.Empty>(); } \ No newline at end of file diff --git a/tests/Ooze.Typed.Tests/SorterHandlerTests.cs b/tests/Ooze.Typed.Tests/SorterHandlerTests.cs index ade7aaf..aff5abb 100644 --- a/tests/Ooze.Typed.Tests/SorterHandlerTests.cs +++ b/tests/Ooze.Typed.Tests/SorterHandlerTests.cs @@ -48,20 +48,20 @@ public void Should_Update_Query_When_Sorters_Are_Present() class SUT { - public IOozeSorterProvider Provider1 { get; } - public IOozeSorterProvider Provider2 { get; } + public ISorterProvider Provider1 { get; } + public ISorterProvider Provider2 { get; } public IQueryable Query { get; } - public IOozeSorterHandler Handler { get; } - private ILogger> Log { get; } + public ISorterHandler Handler { get; } + private ILogger> Log { get; } public SUT() { - Provider1 = Substitute.For>(); - Provider2 = Substitute.For>(); - Log = Substitute.For>>(); + Provider1 = Substitute.For>(); + Provider2 = Substitute.For>(); + Log = Substitute.For>>(); Query = Enumerable.Empty().AsQueryable(); - Handler = new OozeSorterHandler(new[] { Provider1, Provider2 }, Log); + Handler = new SorterHandler(new[] { Provider1, Provider2 }, Log); } } } \ No newline at end of file diff --git a/tests/Ooze.Typed.Web/Controllers/BlogController.cs b/tests/Ooze.Typed.Web/Controllers/BlogController.cs index 3f31c28..9b94c79 100644 --- a/tests/Ooze.Typed.Web/Controllers/BlogController.cs +++ b/tests/Ooze.Typed.Web/Controllers/BlogController.cs @@ -13,16 +13,16 @@ public class BlogController : ControllerBase private readonly SqlServerDatabaseContext _sqlServerDb; private readonly PostgresDatabaseContext _postgresDb; private readonly MariaDbDatabaseContext _mariaDb; - private readonly IOozeTypedResolver _nonTypedResolver; - private readonly IOozeTypedResolver _resolver; + private readonly IOperationResolver _nonTypedResolver; + private readonly IOperationResolver _resolver; public BlogController( DatabaseContext db, SqlServerDatabaseContext sqlServerDb, PostgresDatabaseContext postgresDb, MariaDbDatabaseContext mariaDb, - IOozeTypedResolver nonTypedResolver, - IOozeTypedResolver resolver) + IOperationResolver nonTypedResolver, + IOperationResolver resolver) { _db = db; _sqlServerDb = sqlServerDb; @@ -73,13 +73,19 @@ public async Task PostTypedExpanded(Input model) } [HttpPost("/sql-server")] - public async Task PostSqlServer(Input model) + public async Task PostSqlServer( + [FromServices] IAsyncOperationResolver asyncResolver, + Input model) { IQueryable query = _sqlServerDb.Set(); - query = _nonTypedResolver - .Filter(query, model.Filters); - query = _nonTypedResolver.Sort(query, model.Sorters); + // query = _nonTypedResolver + // .Filter(query, model.Filters); + // query = _nonTypedResolver.Sort(query, model.Sorters); + query = await asyncResolver.WithQuery(query) + .Filter(model.Filters) + .Sort(model.Sorters) + .ApplyAsync(); var results = await query.ToListAsync(); return Ok(results); diff --git a/tests/Ooze.Typed.Web/Controllers/CommentController.cs b/tests/Ooze.Typed.Web/Controllers/CommentController.cs index ab6dc75..afe94c4 100644 --- a/tests/Ooze.Typed.Web/Controllers/CommentController.cs +++ b/tests/Ooze.Typed.Web/Controllers/CommentController.cs @@ -9,11 +9,11 @@ namespace Ooze.Typed.Web.Controllers; public class CommentController : ControllerBase { private readonly DatabaseContext _db; - private readonly IOozeTypedResolver _resolver; + private readonly IOperationResolver _resolver; public CommentController( DatabaseContext db, - IOozeTypedResolver resolver) + IOperationResolver resolver) { _db = db; _resolver = resolver; diff --git a/tests/Ooze.Typed.Web/Filters/OozeFilter.cs b/tests/Ooze.Typed.Web/Filters/OozeFilter.cs index 3f7a9be..153d84d 100644 --- a/tests/Ooze.Typed.Web/Filters/OozeFilter.cs +++ b/tests/Ooze.Typed.Web/Filters/OozeFilter.cs @@ -8,11 +8,11 @@ namespace Ooze.Typed.Web.Filters; public sealed class OozeFilter : IAsyncResultFilter where TEntity : class { - private readonly IOozeTypedResolver _resolver; + private readonly IOperationResolver _resolver; private readonly ILogger> _log; public OozeFilter( - IOozeTypedResolver resolver, + IOperationResolver resolver, ILogger> log) { _resolver = resolver; diff --git a/tests/Ooze.Typed.Web/OozeConfiguration/BlogFiltersProvider.cs b/tests/Ooze.Typed.Web/OozeConfiguration/BlogFiltersProvider.cs index 1de5057..e175de9 100644 --- a/tests/Ooze.Typed.Web/OozeConfiguration/BlogFiltersProvider.cs +++ b/tests/Ooze.Typed.Web/OozeConfiguration/BlogFiltersProvider.cs @@ -1,8 +1,9 @@ using Ooze.Typed.EntityFrameworkCore.Extensions; using Ooze.Typed.Filters; +using Ooze.Typed.Filters.Async; using Ooze.Typed.Web.Entities; -public class BlogFiltersProvider : IOozeFilterProvider +public class BlogFiltersProvider : IFilterProvider, IAsyncFilterProvider { private readonly IHttpContextAccessor _httpContextAccessor; @@ -11,7 +12,7 @@ public BlogFiltersProvider(IHttpContextAccessor httpContextAccessor) _httpContextAccessor = httpContextAccessor; } - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() { var httpContext = _httpContextAccessor.HttpContext; var hasSecretParam = !httpContext?.Request.Query.ContainsKey("secret") ?? true; @@ -23,4 +24,23 @@ public IEnumerable> GetFilters() .Like(blog => blog.Name, filter => filter.Name) .Build(); } + + public ValueTask>> GetFiltersAsync() + { + var filters = AsyncFilters.CreateFor() + .Add(filter => string.IsNullOrEmpty(filter.Name) == false, filter => blog => blog.Name == filter.Name) + .AddAsync(async filter => + { + await Task.Delay(1); + return string.IsNullOrEmpty(filter.Name) == false; + }, + async filter => + { + await Task.Delay(1); + return blog => blog.Name == filter.Name; + }) + .Build(); + + return ValueTask.FromResult(filters); + } } diff --git a/tests/Ooze.Typed.Web/OozeConfiguration/BlogSortersProvider.cs b/tests/Ooze.Typed.Web/OozeConfiguration/BlogSortersProvider.cs index 7a3d56d..e41e4a7 100644 --- a/tests/Ooze.Typed.Web/OozeConfiguration/BlogSortersProvider.cs +++ b/tests/Ooze.Typed.Web/OozeConfiguration/BlogSortersProvider.cs @@ -2,9 +2,9 @@ using Ooze.Typed.Sorters; using Ooze.Typed.Web.Entities; -public class BlogSortersProvider : IOozeSorterProvider +public class BlogSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() { return Sorters.CreateFor() .SortBy(blog => blog.Id, sort => sort.BlogIdSort) @@ -21,9 +21,9 @@ public class CommentFilters public RangeFilter CommentIdsRange { get; set; } = default!; } -public class CommentFiltersProvider : IOozeFilterProvider +public class CommentFiltersProvider : IFilterProvider { - public IEnumerable> GetFilters() + public IEnumerable> GetFilters() { return Filters.CreateFor() .StartsWith(comment => comment.Text, filter => filter.Name) @@ -41,9 +41,9 @@ public class CommentSorters public SortDirection? NameSort { get; set; } } -public class CommentsSortersProvider : IOozeSorterProvider +public class CommentsSortersProvider : ISorterProvider { - public IEnumerable> GetSorters() + public IEnumerable> GetSorters() { return Sorters.CreateFor() .SortBy(comment => comment.Post.Id, sort => sort.IdSort) diff --git a/tests/Ooze.Typed.Web/Program.cs b/tests/Ooze.Typed.Web/Program.cs index 0cc1850..e55b25b 100644 --- a/tests/Ooze.Typed.Web/Program.cs +++ b/tests/Ooze.Typed.Web/Program.cs @@ -18,6 +18,7 @@ opts.UseMySql(builder.Configuration.GetConnectionString("MariaDb"), serverVersion).EnableSensitiveDataLogging()); builder.Services.AddHostedService(); builder.Services.AddOozeTyped() + .EnableAsyncResolvers() .Add() .Add() .Add()