From ceae36ee4b541c0c6128d986bdd035cd9c52a3d4 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri <7004080+alirezanet@users.noreply.github.com> Date: Sat, 20 Nov 2021 02:57:32 +0330 Subject: [PATCH] v2.4.3 (#44) * Rename Evaluator build methods Improve documentation * update to v2.4.3 * fix BuildFilteringExpression benchmark name * add Evaluator test (in-memory db) * update Compile and Reuse examples * update Compile and Reuse description * update AddCondition documentation --- README.md | 72 ++++++++++--------- benchmark/QueryBuilderBuildBenchmark.cs | 2 +- .../Gridify.EntityFramework.csproj | 2 +- src/Gridify/Gridify.csproj | 2 +- src/Gridify/IQueryBuilder.cs | 28 +++++++- src/Gridify/QueryBuilder.cs | 16 +++-- .../DatabaseFixture.cs | 37 ++++++++++ .../GridifyEntityFrameworkTests.cs | 56 ++++++++------- test/Gridify.Tests/QueryBuilderShould.cs | 4 +- 9 files changed, 146 insertions(+), 73 deletions(-) create mode 100644 test/EntityFrameworkIntegrationTests/DatabaseFixture.cs diff --git a/README.md b/README.md index 65b46a48..d6d35db8 100644 --- a/README.md +++ b/README.md @@ -322,36 +322,6 @@ Also, I recommended to Enable EntityFramework compatibility layer if you using g ``` --- -## Compile and Reuse -You can get Gridify generated expressions using the `GetFilteringExpression` and `GetOrderingExpression` methods, so you can store an expression and use it multiple times without having any overheads, also if you compile an expression you get a massive performance boost. but you should only use a compiled expression if you are not using Gridify alongside an ORM like Entity-Framework. -eg: -```c# -var gm = new GridifyMapper().GenerateMappings(); -var gq = new GridifyQuery() {Filter = "name=John"}; -var expression = gq.GetFilteringExpression(gm); -var compiledExpression = expression.Compile(); -``` - -**expression usage:** -```c# -myDbContext.Persons.Where(expression); -``` - -**compiled expression usage:** -```c# -myPersonList.Where(compiledExpression); -``` - - -This is the performance improvement example when you use a compiled expression - -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated | -|---------------- |-------------:|-----------:|-----------:|------:|--------:|---------:|--------:|----------:| -| GridifyCompiled | 1.008 us | 0.0035 us | 0.0031 us | 0.001 | 0.00 | 0.1564 | - | 984 B | -| NativeLinQ | 724.329 us | 6.4686 us | 6.0507 us | 1.000 | 0.00 | 5.8594 | 2.9297 | 37,392 B | -| Gridify | 736.854 us | 5.7427 us | 5.0907 us | 1.018 | 0.01 | 5.8594 | 2.9297 | 39,924 B | ---- - ## QueryBuilder @@ -371,8 +341,8 @@ The QueryBuilder class is really useful if you want to manually build your query | Build | Applies filtering ordering and paging to a queryable context | | BuildCompiled | Compiles the expressions and returns a delegate for applying filtering ordering and paging to a enumerable collection | | BuildFilteringExpression | Returns filtering expression that can be compiled for later use for enumerable collections | -| BuildQueryableEvaluator | Returns an evaluator delegate that can be use to evaluate an queryable context | -| BuildCollectionEvaluator | Returns an evaluator delegate that can be use to evaluate an enumerable context | +| BuildEvaluator | Returns an evaluator delegate that can be use to evaluate an queryable context | +| BuildCompiledEvaluator | Returns an compiled evaluator delegate that can be use to evaluate an enumerable collection | | BuildWithPaging | Applies filtering ordering and paging to a context, and returns paging result | | BuildWithPagingCompiled | Compiles the expressions and returns a delegate for applying filtering ordering and paging to a enumerable collection, that returns paging result | | BuildWithQueryablePaging | Applies filtering ordering and paging to a context, and returns queryable paging result | @@ -389,6 +359,44 @@ var builder = new QueryBuilder() --- +## Compile and Reuse +You can access Gridify generated expressions using the `GetFilteringExpression` of `GridifyQuery` or `BuildCompiled` methods of `QueryBuilder` class, +by storing an expression you can use it multiple times without having any overheads, +also if you store a compiled expression you get a massive performance boost. + +**Important note**: you should only use a **compiled** expression if you are **not** using Gridify alongside an ORM like Entity-Framework. + +```c# + // eg.1 - using GridifyQuery - Compield - where only ------------------ + var gq = new GridifyQuery() { Filter = "name=John" }; + var expression = gq.GetFilteringExpression(); + var compiledExpression = expression.Compile(); + var result = persons.Where(compiledExpression); + + // eg.2 - using QueryBuilder - Compield - where only ------------------ + var compiledExpression = new QueryBuilder() + .AddCondition("name=John") + .BuildFilteringExpression() + .Compile(); + var result = persons.Where(compiledExpression); + + // eg.3 - using QueryBuilder - BuildCompiled ------------------------- + var func = new QueryBuilder() + .AddCondition("name=John") + .BuildCompiled(); + var result = func(persons); + +``` + +This is the performance improvement example when you use a compiled expression + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated | +|---------------- |-------------:|-----------:|-----------:|------:|--------:|---------:|--------:|----------:| +| GridifyCompiled | 1.008 us | 0.0035 us | 0.0031 us | 0.001 | 0.00 | 0.1564 | - | 984 B | +| NativeLINQ | 724.329 us | 6.4686 us | 6.0507 us | 1.000 | 0.00 | 5.8594 | 2.9297 | 37,392 B | +| Gridify | 736.854 us | 5.7427 us | 5.0907 us | 1.018 | 0.01 | 5.8594 | 2.9297 | 39,924 B | +--- + ## Combine Gridify with AutoMapper ```c# diff --git a/benchmark/QueryBuilderBuildBenchmark.cs b/benchmark/QueryBuilderBuildBenchmark.cs index f77e6001..c7e46486 100644 --- a/benchmark/QueryBuilderBuildBenchmark.cs +++ b/benchmark/QueryBuilderBuildBenchmark.cs @@ -41,7 +41,7 @@ public QueryBuilderBuildBenchmark() } [Benchmark(Baseline = true)] // this method is only for filtering operations - public void UseGetFilteringExpression() + public void BuildFilteringExpression() { _data.Where(BuildFilteringExpressionFunc).Consume(Consumer); } diff --git a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj index e4e8aa66..15e47d07 100644 --- a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj +++ b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj @@ -9,7 +9,7 @@ netstandard2.0 Gridify.EntityFramework - 2.4.2 + 2.4.3 Alireza Sabouri TuxTeam Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/Gridify.csproj b/src/Gridify/Gridify.csproj index f8ecf979..5e19184b 100644 --- a/src/Gridify/Gridify.csproj +++ b/src/Gridify/Gridify.csproj @@ -3,7 +3,7 @@ netstandard2.0 Gridify - 2.4.2 + 2.4.3 Alireza Sabouri TuxTeam Gridify, Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/IQueryBuilder.cs b/src/Gridify/IQueryBuilder.cs index 8d050c7c..467925aa 100644 --- a/src/Gridify/IQueryBuilder.cs +++ b/src/Gridify/IQueryBuilder.cs @@ -32,6 +32,8 @@ public interface IQueryBuilder /// /// Using this method you can add gridify supported string base filtering statements + /// Each added condition can be use to evaluate a context, also all conditions will be + /// ANDed together for filtering. /// /// (Name=John,Age>10) /// string based filtering @@ -60,9 +62,31 @@ public interface IQueryBuilder IQueryBuilder AddMap(string from, Expression> to, Func? convertor = null, bool overwrite = true); IQueryBuilder RemoveMap(IGMap map); Expression> BuildFilteringExpression(); - Func, bool> BuildQueryableEvaluator(); - Func, bool> BuildCollectionEvaluator(); + + /// + /// Creates an evaluator delegate that can be use to evaluate an queryable context + /// + /// A delegate as type , bool>]]> + Func, bool> BuildEvaluator(); + + /// + /// Creates an compiled evaluator delegate that can be use to evaluate an enumerable collection + /// + /// A delegate as type , bool>]]> + Func, bool> BuildCompiledEvaluator(); + + /// + /// Directly Evaluate a queryable context to check if all conditions are valid or not + /// + /// + /// bool Evaluate(IQueryable query); + + /// + /// Directly Evaluate a collection to check if all conditions are valid or not + /// + /// + /// bool Evaluate(IEnumerable collection); /// diff --git a/src/Gridify/QueryBuilder.cs b/src/Gridify/QueryBuilder.cs index 4e92772c..3c9393c9 100644 --- a/src/Gridify/QueryBuilder.cs +++ b/src/Gridify/QueryBuilder.cs @@ -137,7 +137,7 @@ public Expression> BuildFilteringExpression() } /// - public Func, bool> BuildQueryableEvaluator() + public Func, bool> BuildEvaluator() { return collection => { @@ -148,26 +148,28 @@ public Func, bool> BuildQueryableEvaluator() } /// - public Func, bool> BuildCollectionEvaluator() + public Func, bool> BuildCompiledEvaluator() { + var compiledCond = _conditions.Select(q => q.Compile() as Func); + var length = _conditions.Count; return collection => { - return _conditions.Count == 0 || - _conditions.Aggregate(true, (current, expression) - => current & collection.Any((expression as Expression>)!.Compile())); + return length == 0 || + compiledCond.Aggregate(true, (current, expression) + => current && collection.Any(expression!)); }; } /// public bool Evaluate(IQueryable query) { - return BuildQueryableEvaluator()(query); + return BuildEvaluator()(query); } /// public bool Evaluate(IEnumerable collection) { - return BuildCollectionEvaluator()(collection); + return BuildCompiledEvaluator()(collection); } /// diff --git a/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs b/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs new file mode 100644 index 00000000..9bfba6d6 --- /dev/null +++ b/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using Xunit; + +namespace EntityFrameworkIntegrationTests.cs +{ + public class DatabaseFixture : IDisposable + { + public DatabaseFixture() + { + _dbContext = new MyDbContext(); + AddTestUsers(); + // ... initialize data in the test database ... + } + + public void Dispose() + { + _dbContext.Dispose(); + } + + public MyDbContext _dbContext { get; private set; } + private void AddTestUsers() + { + Assert.Equal(0,_dbContext.Users.Count()); + _dbContext.Users.AddRange( + new User { Id = 1, Name = "ahmad" }, + new User { Id = 2, Name = "ali" }, + new User { Id = 3, Name = "vahid" }, + new User { Id = 4, Name = "hamid" }, + new User { Id = 5, Name = "Hamed" }, + new User { Id = 6, Name = "sara" }, + new User { Id = 7, Name = "Ali" }); + + _dbContext.SaveChanges(); + } + } +} diff --git a/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs b/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs index fd54792b..1327572b 100644 --- a/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs +++ b/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs @@ -1,20 +1,18 @@ -using System; -using System.Linq; +using System.Linq; using Gridify; using Xunit; namespace EntityFrameworkIntegrationTests.cs { - public class GridifyEntityFrameworkTests + public class GridifyEntityFrameworkTests : IClassFixture { - private readonly MyDbContext _dbContext; + private readonly DatabaseFixture fixture; + private MyDbContext _ctx => fixture._dbContext; - public GridifyEntityFrameworkTests() + public GridifyEntityFrameworkTests(DatabaseFixture fixture) { - _dbContext = new MyDbContext(); - AddTestUsers(); + this.fixture = fixture; } - [Fact] public void EntityFrameworkServiceProviderCachingShouldNotThrowException() { @@ -23,11 +21,11 @@ public void EntityFrameworkServiceProviderCachingShouldNotThrowException() // arrange var gq = new GridifyQuery { Filter = "name=n1|name=n2" }; - _dbContext.Users.Gridify(gq); - _dbContext.Users.Gridify(gq); + _ctx.Users.Gridify(gq); + _ctx.Users.Gridify(gq); //act - var exception = Record.Exception(() => _dbContext.Users.GridifyQueryable(gq)); + var exception = Record.Exception(() => _ctx.Users.GridifyQueryable(gq)); // assert Assert.Null(exception); @@ -40,7 +38,7 @@ public void GridifyQueryableDateTimeShouldNotThrowException() var gq = new GridifyQuery { OrderBy = "CreateDate" }; // act - var exception = Record.Exception(() => _dbContext.Users.GridifyQueryable(gq)); + var exception = Record.Exception(() => _ctx.Users.GridifyQueryable(gq)); // assert Assert.Null(exception); @@ -56,27 +54,31 @@ public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF() { GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); - var actual = _dbContext.Users.ApplyFiltering("name > h").ToList(); - var expected = _dbContext.Users.Where(q => string.Compare(q.Name, "h") > 0).ToList(); + var actual = _ctx.Users.ApplyFiltering("name > h").ToList(); + var expected = _ctx.Users.Where(q => string.Compare(q.Name, "h") > 0).ToList(); Assert.Equal(expected.Count, actual.Count); Assert.Equal(expected, actual); Assert.True(actual.Any()); } - - private void AddTestUsers() + [Fact] + public void Builder_BuildEvaluator_Should_Correctly_Evaluate_All_Conditions() { - _dbContext.Users.AddRange( - new User() { Name = "ahmad" }, - new User() { Name = "ali" }, - new User() { Name = "vahid" }, - new User() { Name = "hamid" }, - new User() { Name = "Hamed" }, - new User() { Name = "sara" }, - new User() { Name = "Ali" }); - - _dbContext.SaveChanges(); + var builder = new QueryBuilder() + .AddCondition("name=*a") + .AddCondition("id>3"); + + var evaluator = builder.BuildEvaluator(); + var actual = evaluator(_ctx.Users); + + Assert.True(actual); + Assert.True(builder.Evaluate(_ctx.Users)); + + builder.AddCondition("name=fakeName"); + Assert.False(builder.Evaluate(_ctx.Users)); + } + } -} \ No newline at end of file +} diff --git a/test/Gridify.Tests/QueryBuilderShould.cs b/test/Gridify.Tests/QueryBuilderShould.cs index 3d914c4d..3264c21f 100644 --- a/test/Gridify.Tests/QueryBuilderShould.cs +++ b/test/Gridify.Tests/QueryBuilderShould.cs @@ -40,11 +40,11 @@ public void Evaluator_Should_Check_All_Conditions_Without_And() .AddCondition("name =Sara, Id > 6"); // using CollectionEvaluator - var evaluator = builder.BuildCollectionEvaluator(); + var evaluator = builder.BuildCompiledEvaluator(); Assert.True(evaluator(_fakeRepository)); // using QueryableEvaluator - var queryableEvaluator = builder.BuildQueryableEvaluator(); + var queryableEvaluator = builder.BuildEvaluator(); Assert.True(queryableEvaluator(_fakeRepository.AsQueryable())); // Using Evaluate method (collection)