Skip to content

Commit

Permalink
Improving QueryBuilder (#43)
Browse files Browse the repository at this point in the history
* Add Build overloads take no parameter

Add Build method documentations

* remove compiled build methods

* Add BuildCompiled method

* add BuildFilteringExpression test

* add QueryBuilderBuildBenchmark

* remove test todo

* BuildOrderingExpression removed.

this method is not useful because we don't know ordering is ascending or descending, also simple use the output expressions on LINQ OrderBy methods doesn't work because we need to use ThenBy on second orderings.

* remove orderBy from QueryBuilderBuildBenchmark

* update to v2.4.2

* fix BuildWithPagingCompiled

* Add BuildWithPagingCompiled

* fix description
  • Loading branch information
alirezanet authored Nov 19, 2021
1 parent a020b60 commit 406b518
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 39 deletions.
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,25 +357,26 @@ This is the performance improvement example when you use a compiled expression

The QueryBuilder class is really useful if you want to manually build your query, also when you don't want to use the extension methods.

| Method | Description |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| AddCondition | Adds a string base Filtering query |
| AddOrderBy | Adds a string base Ordering query |
| ConfigurePaging | Configure Page and PageSize |
| AddQuery | Accepts a GridifyQuery object to configure filtering,ordering and paging |
| UseCustomMapper | Accepts a GridifyMapper to use in build methods |
| UseEmptyMapper | Setup an Empty new GridifyMapper without auto generated mappings |
| AddMap | Add a single Map to existing mapper |
| RemoveMap | Remove a single Map from existing mapper |
| ConfigureDefaultMapper | Configuring default mapper when we didn't use AddMapper method |
| Build | Applies filtering ordering and paging to a context |
| BuildFilteringExpression | Returns filtering expression |
| BuildOrderingExpression | Returns ordering expression |
| 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 |
| BuildWithPaging | Applies filtering ordering and paging to a context, and returns paging result |
| BuildWithQueryablePaging | Applies filtering ordering and paging to a context, and returns queryable paging result |
| Evaluate | Directly Evaluate a context to check if all conditions are valid or not |
| Method | Description |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| AddCondition | Adds a string base Filtering query |
| AddOrderBy | Adds a string base Ordering query |
| ConfigurePaging | Configure Page and PageSize |
| AddQuery | Accepts a GridifyQuery object to configure filtering,ordering and paging |
| UseCustomMapper | Accepts a GridifyMapper to use in build methods |
| UseEmptyMapper | Setup an Empty new GridifyMapper without auto generated mappings |
| AddMap | Add a single Map to existing mapper |
| RemoveMap | Remove a single Map from existing mapper |
| ConfigureDefaultMapper | Configuring default mapper when we didn't use AddMapper method |
| 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 |
| 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 |
| Evaluate | Directly Evaluate a context to check if all conditions are valid or not |

usage eg:
```c#
Expand Down
4 changes: 2 additions & 2 deletions benchmark/LibraryComparisionFilteringBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public void Sieve()
}


private static IEnumerable<TestClass> GetSampleData()
public static IEnumerable<TestClass> GetSampleData()
{
var lst = new List<TestClass>();
lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now));
Expand Down Expand Up @@ -129,4 +129,4 @@ private static IEnumerable<TestClass> GetSampleData()
return lst;
}
}
}
}
6 changes: 3 additions & 3 deletions benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ public class Program
{
private static void Main()
{
BenchmarkRunner.Run<LibraryComparisionFilteringBenchmark>();
// BenchmarkRunner.Run<LibraryComparisionFilteringBenchmark>();
BenchmarkRunner.Run<QueryBuilderBuildBenchmark>();
Console.Read();
}

}
}
}
100 changes: 100 additions & 0 deletions benchmark/QueryBuilderBuildBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Order;
using Gridify;
using Gridify.Tests;

namespace Benchmarks
{
[MemoryDiagnoser]
[RPlotExporter]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class QueryBuilderBuildBenchmark
{
private readonly IEnumerable<TestClass> _data;
private static readonly Consumer Consumer = new();
private readonly Func<IEnumerable<TestClass>, IEnumerable<TestClass>> BuildCompiledFuc;
private readonly Func<IQueryable<TestClass>, IQueryable<TestClass>> BuildFunc;
private readonly Func<TestClass, bool> BuildFilteringExpressionFunc;
private readonly Func<IEnumerable<TestClass>, Paging<TestClass>> BuildWithPagingCompiledFunc;
private readonly Func<IQueryable<TestClass>, Paging<TestClass>> BuildWithPagingFunc;


public QueryBuilderBuildBenchmark()
{
_data = LibraryComparisionFilteringBenchmark.GetSampleData().ToArray();

var builder = new QueryBuilder<TestClass>()
.AddCondition("id>2")
.AddCondition("name=*a");

BuildCompiledFuc = builder.BuildCompiled();
BuildFunc = builder.Build();
BuildFilteringExpressionFunc = builder.BuildFilteringExpression().Compile();
BuildWithPagingCompiledFunc = builder.BuildWithPagingCompiled();
BuildWithPagingFunc = builder.BuildWithPaging();

TestOutputs();
}

[Benchmark(Baseline = true)] // this method is only for filtering operations
public void UseGetFilteringExpression()
{
_data.Where(BuildFilteringExpressionFunc).Consume(Consumer);
}

[Benchmark]
public void Build()
{
BuildFunc(_data.AsQueryable()).Consume(Consumer);
}

[Benchmark]
public void BuildCompiled()
{
BuildCompiledFuc(_data).Consume(Consumer);
}

[Benchmark]
public void BuildWithPaging()
{
BuildWithPagingFunc(_data.AsQueryable()).Data.Consume(Consumer);
}

[Benchmark]
public void BuildWithPagingCompiled()
{
BuildWithPagingCompiledFunc(_data.AsQueryable()).Data.Consume(Consumer);
}

private void TestOutputs()
{
if (AllSame(BuildCompiledFuc(_data).Count(), BuildFunc(_data.AsQueryable()).Count(), _data.Where(BuildFilteringExpressionFunc).Count()) &&
AllSame(BuildCompiledFuc(_data).First().Id, BuildFunc(_data.AsQueryable()).First().Id,
_data.Where(BuildFilteringExpressionFunc).First().Id) &&
AllSame(BuildCompiledFuc(_data).Last().Id, BuildFunc(_data.AsQueryable()).Last().Id,
_data.Where(BuildFilteringExpressionFunc).Last().Id) &&
BuildCompiledFuc(_data).Count() < 2)
{
throw new Exception("MISS MATCH OUTPUT");
}
}

private static bool AllSame<T>(params T[] items)
{
var first = true;
T comparand = default;
foreach (var i in items)
{
if (first) comparand = i;
else if (!i.Equals(comparand)) return false;
first = false;
}

return true;
}
}
}
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.1</Version>
<Version>2.4.2</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.1</Version>
<Version>2.4.2</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
1 change: 0 additions & 1 deletion src/Gridify/GridifyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ private static IGridifyPagination FixPagingData(this IGridifyPagination gridifyP

#endregion

// TODO: should have some tests
public static Expression<Func<T, bool>> GetFilteringExpression<T>(this IGridifyFiltering gridifyFiltering, IGridifyMapper<T>? mapper = null)
{
if (string.IsNullOrWhiteSpace(gridifyFiltering.Filter))
Expand Down
128 changes: 127 additions & 1 deletion src/Gridify/IQueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,141 @@ 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();
IEnumerable<Expression<Func<T, object>>> BuildOrderingExpression();
Func<IQueryable<T>, bool> BuildQueryableEvaluator();
Func<IEnumerable<T>, bool> BuildCollectionEvaluator();
bool Evaluate(IQueryable<T> query);
bool Evaluate(IEnumerable<T> collection);

/// <summary>
/// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable.
/// </summary>
/// <example>
/// <code>
/// var func = builder.Build();
/// var query = func(queryableContext);
/// </code>
/// </example>
/// <returns>A delegate as type <![CDATA[Func<IQueryable<T>, IQueryable<T>>]]></returns>
Func<IQueryable<T>, IQueryable<T>> Build();

/// <summary>
/// Returns a delegate that can be used to apply the filtering, ordering and paging to a collection.
/// also, Internally it compiles the expressions to increase performance.
/// </summary>
/// <example>
/// <code>
/// var func = builder.BuildCompiled();
/// var result = func(enumerableCollection);
/// </code>
/// </example>
/// <returns>A delegate as type <![CDATA[Func<IEnumerable<T>, IEnumerable<T>>]]></returns>
Func<IEnumerable<T>, IEnumerable<T>> BuildCompiled();

/// <summary>
/// Directly applies the filtering, ordering and paging to a queryable.
/// </summary>
/// <example>
/// <code>
/// var query = builder.Build(queryableContext);
/// </code>
/// </example>
/// <returns><![CDATA[IQueryable<T>]]></returns>
IQueryable<T> Build(IQueryable<T> context);

/// <summary>
/// Directly applies the filtering, ordering and paging to a enumerable collection.
/// </summary>
/// <example>
/// <code>
/// var result = builder.Build(enumerableCollection);
/// </code>
/// </example>
/// <returns><![CDATA[IEnumerable<T>]]></returns>
IEnumerable<T> Build(IEnumerable<T> collection);

/// <summary>
/// Directly applies the filtering, ordering and paging to a enumerable collection.
/// also returns the total count of the collection.
/// </summary>
/// <example>
/// <code>
/// var pagingResult = builder.BuildWithPaging(enumerableCollection);
/// // or
/// var (count, result) = builder.BuildWithPaging(enumerableCollection);
/// </code>
/// </example>
/// <returns><![CDATA[Paging<T>]]></returns>
Paging<T> BuildWithPaging(IEnumerable<T> collection);

/// <summary>
/// Directly applies the filtering, ordering and paging to a queryable.
/// also load the data and returns the total count of the records.
/// </summary>
/// <example>
/// <code>
/// var pagingResult = builder.BuildWithPaging(queryableContext);
/// // or
/// var (count, result) = builder.BuildWithPaging(queryableContext);
/// </code>
/// </example>
/// <returns><![CDATA[Paging<T>]]></returns>
Paging<T> BuildWithPaging(IQueryable<T> collection);

/// <summary>
/// Directly applies the filtering, ordering and paging to a queryable.
/// also returns the total count of the records.
/// </summary>
/// <example>
/// <code>
/// var queryablePaging = builder.BuildWithQueryablePaging(queryableContext);
/// // or
/// var (count, query) = builder.BuildWithQueryablePaging(queryableContext);
/// </code>
/// </example>
/// <returns><![CDATA[QueryablePaging<T>]]></returns>
QueryablePaging<T> BuildWithQueryablePaging(IQueryable<T> collection);

/// <summary>
/// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable.
/// also returns the total count of the records.
/// </summary>
/// <example>
/// <code>
/// var func = builder.BuildWithQueryablePaging();
/// var query = func(queryableContext);
/// </code>
/// </example>
/// <returns>A delegate as type <![CDATA[Func<IQueryable<T>,QueryablePaging<T>>]]></returns>
Func<IQueryable<T>,QueryablePaging<T>> BuildWithQueryablePaging();

/// <summary>
/// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable.
/// also load the data and returns the total count of the records.
/// </summary>
/// <example>
/// <code>
/// var func = builder.BuildWithPaging();
/// var pagingQuery = func(queryableContext);
/// // or
/// var (count, query) = func(queryableContext);
/// </code>
/// </example>
/// <returns><![CDATA[ Func<IQueryable<T>,Paging<T>> ]]></returns>
Func<IQueryable<T>,Paging<T>> BuildWithPaging();

/// <summary>
/// Returns a delegate that can be used to apply the filtering, ordering and paging to a enumerable collection.
/// also load the data and returns the total count of the records.
/// </summary>
/// <example>
/// <code>
/// var func = builder.BuildWithPagingAsEnumerable();
/// var pagingResult = func(enumerableCollection);
/// // or
/// var (count, result) = func(enumerableCollection);
/// </code>
/// </example>
/// <returns><![CDATA[ Func<IQueryable<T>,Paging<T>> ]]></returns>
Func<IEnumerable<T>,Paging<T>> BuildWithPagingCompiled();
}
}
Loading

0 comments on commit 406b518

Please sign in to comment.