Skip to content

Commit

Permalink
v2.4.3 (#44)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alirezanet authored Nov 19, 2021
1 parent 406b518 commit ceae36e
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 73 deletions.
72 changes: 40 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Person>().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

Expand All @@ -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 |
Expand All @@ -389,6 +359,44 @@ var builder = new QueryBuilder<Person>()

---

## 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<Person>();
var compiledExpression = expression.Compile();
var result = persons.Where(compiledExpression);

// eg.2 - using QueryBuilder - Compield - where only ------------------
var compiledExpression = new QueryBuilder<Person>()
.AddCondition("name=John")
.BuildFilteringExpression()
.Compile();
var result = persons.Where(compiledExpression);

// eg.3 - using QueryBuilder - BuildCompiled -------------------------
var func = new QueryBuilder<Person>()
.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#
Expand Down
2 changes: 1 addition & 1 deletion benchmark/QueryBuilderBuildBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify.EntityFramework/Gridify.EntityFramework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Gridify.EntityFramework</PackageId>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<Authors>Alireza Sabouri</Authors>
<Company>TuxTeam</Company>
<PackageDescription>Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data.</PackageDescription>
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify/Gridify.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Gridify</PackageId>
<Version>2.4.2</Version>
<Version>2.4.3</Version>
<Authors>Alireza Sabouri</Authors>
<Company>TuxTeam</Company>
<PackageDescription>Gridify, Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data.</PackageDescription>
Expand Down
28 changes: 26 additions & 2 deletions src/Gridify/IQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public interface IQueryBuilder<T>

/// <summary>
/// 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.
/// </summary>
/// <example> (Name=John,Age>10) </example>
/// <param name="condition">string based filtering</param>
Expand Down Expand Up @@ -60,9 +62,31 @@ public interface IQueryBuilder<T>
IQueryBuilder<T> AddMap(string from, Expression<Func<T, object?>> to, Func<string, object>? convertor = null, bool overwrite = true);
IQueryBuilder<T> RemoveMap(IGMap<T> map);
Expression<Func<T, bool>> BuildFilteringExpression();
Func<IQueryable<T>, bool> BuildQueryableEvaluator();
Func<IEnumerable<T>, bool> BuildCollectionEvaluator();

/// <summary>
/// Creates an evaluator delegate that can be use to evaluate an queryable context
/// </summary>
/// <returns>A delegate as type <![CDATA[Func<IQueryable<T>, bool>]]></returns>
Func<IQueryable<T>, bool> BuildEvaluator();

/// <summary>
/// Creates an compiled evaluator delegate that can be use to evaluate an enumerable collection
/// </summary>
/// <returns>A delegate as type <![CDATA[Func<IEnumerable<T>, bool>]]></returns>
Func<IEnumerable<T>, bool> BuildCompiledEvaluator();

/// <summary>
/// Directly Evaluate a queryable context to check if all conditions are valid or not
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
bool Evaluate(IQueryable<T> query);

/// <summary>
/// Directly Evaluate a collection to check if all conditions are valid or not
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
bool Evaluate(IEnumerable<T> collection);

/// <summary>
Expand Down
16 changes: 9 additions & 7 deletions src/Gridify/QueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public Expression<Func<T, bool>> BuildFilteringExpression()
}

/// <inheritdoc />
public Func<IQueryable<T>, bool> BuildQueryableEvaluator()
public Func<IQueryable<T>, bool> BuildEvaluator()
{
return collection =>
{
Expand All @@ -148,26 +148,28 @@ public Func<IQueryable<T>, bool> BuildQueryableEvaluator()
}

/// <inheritdoc />
public Func<IEnumerable<T>, bool> BuildCollectionEvaluator()
public Func<IEnumerable<T>, bool> BuildCompiledEvaluator()
{
var compiledCond = _conditions.Select(q => q.Compile() as Func<T, bool>);
var length = _conditions.Count;
return collection =>
{
return _conditions.Count == 0 ||
_conditions.Aggregate(true, (current, expression)
=> current & collection.Any((expression as Expression<Func<T, bool>>)!.Compile()));
return length == 0 ||
compiledCond.Aggregate(true, (current, expression)
=> current && collection.Any(expression!));
};
}

/// <inheritdoc />
public bool Evaluate(IQueryable<T> query)
{
return BuildQueryableEvaluator()(query);
return BuildEvaluator()(query);
}

/// <inheritdoc />
public bool Evaluate(IEnumerable<T> collection)
{
return BuildCollectionEvaluator()(collection);
return BuildCompiledEvaluator()(collection);
}

/// <inheritdoc />
Expand Down
37 changes: 37 additions & 0 deletions test/EntityFrameworkIntegrationTests/DatabaseFixture.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
56 changes: 29 additions & 27 deletions test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs
Original file line number Diff line number Diff line change
@@ -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<DatabaseFixture>
{
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()
{
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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<User>()
.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));

}

}
}
}
4 changes: 2 additions & 2 deletions test/Gridify.Tests/QueryBuilderShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ceae36e

Please sign in to comment.