Skip to content

Commit

Permalink
update to v2.3.0 (#30)
Browse files Browse the repository at this point in the history
* Add CancelationToken Support

* fix tests build warnings

* GridifyMapper ToString returns field list

* add GridifyMapper ToString summary comment

* remove the position from SyntaxToken

* add Index mapping feature. fix issue #29

* update to v2.3.0
  • Loading branch information
alirezanet authored Oct 4, 2021
1 parent 179c5ab commit eed8985
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 50 deletions.
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.2.1</Version>
<Version>2.3.0</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
19 changes: 18 additions & 1 deletion src/Gridify.EntityFramework/GridifyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace Gridify.EntityFramework
{
public static partial class GridifyExtensions
{

#region "EntityFramework Integration"

public async static Task<QueryablePaging<T>> GridifyQueryableAsync<T>(this IQueryable<T> query, IGridifyQuery gridifyQuery,
Expand All @@ -26,6 +26,23 @@ public static async Task<Paging<T>> GridifyAsync<T>(this IQueryable<T> query, IG
return new Paging<T>(res.Count, await res.Query.ToListAsync());
}

public async static Task<QueryablePaging<T>> GridifyQueryableAsync<T>(this IQueryable<T> query, IGridifyQuery gridifyQuery,
IGridifyMapper<T> mapper, CancellationToken token)
{
query = query.ApplyFiltering(gridifyQuery, mapper);
var count = await query.CountAsync(token);
query = query.ApplyOrdering(gridifyQuery, mapper);
query = query.ApplyPaging(gridifyQuery);
return new QueryablePaging<T>(count, query);
}

public static async Task<Paging<T>> GridifyAsync<T>(this IQueryable<T> query, IGridifyQuery gridifyQuery, CancellationToken token, IGridifyMapper<T> mapper = null)
{
mapper = mapper.FixMapper();
var res = await query.GridifyQueryableAsync(gridifyQuery, mapper, token);
return new Paging<T>(res.Count, await res.Query.ToListAsync(token));
}

#endregion
}
}
10 changes: 9 additions & 1 deletion src/Gridify/GMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Gridify
public class GMap<T> : IGMap<T>
{
public string From { get; set; }
public Expression<Func<T, object?>> To { get; set; }
public LambdaExpression To { get; set; }
public Func<string, object>? Convertor { get; set; }
public bool IsNestedCollection { get; }

Expand All @@ -17,5 +17,13 @@ public GMap(string from, Expression<Func<T, object?>> to, Func<string, object>?
Convertor = convertor;
IsNestedCollection = isNestedCollection;
}

public GMap(string from, Expression<Func<T,int, object?>> to, Func<string, object>? convertor = null, bool isNestedCollection = false)
{
From = from;
To = to;
Convertor = convertor;
IsNestedCollection = isNestedCollection;
}
}
}
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.2.1</Version>
<Version>2.3.0</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
31 changes: 30 additions & 1 deletion src/Gridify/GridifyMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ public IGridifyMapper<T> AddMap(string from, Expression<Func<T, object?>> to, Fu
return this;
}

public IGridifyMapper<T> AddMap(string from, Expression<Func<T, int, object?>> to, Func<string, object>? convertor = null!,
bool overrideIfExists = true)
{
if (!overrideIfExists && HasMap(from))
throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists");

RemoveMap(from);
var isNested = Regex.IsMatch(to.ToString(), @"\.Select\s*\(");
_mappings.Add(new GMap<T>(from, to, convertor, isNested));
return this;
}

public IGridifyMapper<T> AddMap(IGMap<T> gMap, bool overrideIfExists = true)
{
if (!overrideIfExists && HasMap(gMap.From))
Expand Down Expand Up @@ -96,7 +108,7 @@ public bool HasMap(string from)
: _mappings.FirstOrDefault(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase));
}

public Expression<Func<T, object>> GetExpression(string key)
public LambdaExpression GetLambdaExpression(string key)
{
var expression = Configuration.CaseSensitive
? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To
Expand All @@ -105,12 +117,29 @@ public Expression<Func<T, object>> GetExpression(string key)
throw new GridifyMapperException($"Mapping Key `{key}` not found.");
return expression!;
}

public Expression<Func<T,object?>> GetExpression(string key)
{
var expression = Configuration.CaseSensitive
? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To
: _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase))?.To;
if (expression == null)
throw new GridifyMapperException($"Mapping Key `{key}` not found.");
return expression as Expression<Func<T,object?>> ?? throw new GridifyMapperException($"Expression fir the `{key}` not found.");
}

public IEnumerable<IGMap<T>> GetCurrentMaps()
{
return _mappings;
}

/// <summary>
/// Converts current mappings to a comma seperated list of map names.
/// eg, filed1,field2,field3
/// </summary>
/// <returns>a comma seperated string</returns>
public override string ToString() => string.Join(",", _mappings.Select(q => q.From));

private static Expression<Func<T, object>> CreateExpression(string from)
{
// x =>
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify/IGMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Gridify
public interface IGMap<T>
{
string From { get; set; }
Expression<Func<T, object?>> To { get; set; }
LambdaExpression To { get; set; }
Func<string, object>? Convertor { get; set; }
bool IsNestedCollection { get; }
}
Expand Down
7 changes: 6 additions & 1 deletion src/Gridify/IGridifyMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ namespace Gridify
public interface IGridifyMapper<T>
{
IGridifyMapper<T> AddMap(string from, Expression<Func<T, object?>> to, Func<string, object>? convertor = null, bool overrideIfExists = true);

IGridifyMapper<T> AddMap(string from, Expression<Func<T, int, object?>> to, Func<string, object>? convertor = null!,
bool overrideIfExists = true);

IGridifyMapper<T> AddMap(IGMap<T> gMap, bool overrideIfExists = true);
IGridifyMapper<T> GenerateMappings();
IGridifyMapper<T> RemoveMap(string propertyName);
IGridifyMapper<T> RemoveMap(IGMap<T> gMap);
Expression<Func<T, object>> GetExpression(string from);
LambdaExpression GetLambdaExpression(string from);
Expression<Func<T, object?>> GetExpression(string key);
IGMap<T>? GetGMap(string from);
bool HasMap(string key);
public GridifyMapperConfiguration Configuration { get; }
Expand Down
21 changes: 19 additions & 2 deletions src/Gridify/Syntax/FieldExpressionSyntax.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Gridify.Syntax
{
internal sealed class FieldExpressionSyntax : ExpressionSyntax
{
internal FieldExpressionSyntax(SyntaxToken fieldToken)
{
FieldToken = fieldToken;
// for performance reason we simply check the last character first
if (fieldToken.Text.EndsWith("]"))
{
// checking indexes from the field names
var regex = new Regex(@"(\w+)\[(\d+)\]");
var match = regex.Match(fieldToken.Text);
if (!match.Success) throw new ArgumentException($"Invalid filed name '{fieldToken.Text}'");
IsCollection = true;
Index = int.Parse(match.Groups[2].Value);
FieldToken = new SyntaxToken(SyntaxKind.FieldToken, 0, match.Groups[1].Value);
}
else
FieldToken = fieldToken;
}


public override SyntaxKind Kind => SyntaxKind.FieldExpression;

public override IEnumerable<SyntaxNode> GetChildren()
{
yield return FieldToken;
}

public bool IsCollection { get; }
public int Index { get; }
public SyntaxToken FieldToken { get; }
}
}
2 changes: 1 addition & 1 deletion src/Gridify/Syntax/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public SyntaxToken NextToken()
{
var start = _position;

while (char.IsLetterOrDigit(Current) || Current is '_')
while (char.IsLetterOrDigit(Current) || Current is '_' or '[' or ']')
Next();

var length = _position - start;
Expand Down
2 changes: 1 addition & 1 deletion src/Gridify/Syntax/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private SyntaxToken Match(SyntaxKind kind)
return NextToken();

_diagnostics.Add($"Unexpected token <{Current.Kind}>, expected <{kind}>");
return new SyntaxToken(kind, Current.Position, string.Empty);
return new SyntaxToken(kind, 0, string.Empty);
}

private ExpressionSyntax ParsePrimaryExpression()
Expand Down
6 changes: 2 additions & 4 deletions src/Gridify/Syntax/SyntaxToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,17 @@ namespace Gridify.Syntax
public class SyntaxToken : SyntaxNode
{
public override SyntaxKind Kind { get; }

public int Position { get; }
public string Text { get; }

public override IEnumerable<SyntaxNode> GetChildren()
{
return Enumerable.Empty<SyntaxNode>();
}

public SyntaxToken(SyntaxKind kind, int position, string text)
// we don't need position yet ( we can use the second argument later when we had a analyzer or debugger )
public SyntaxToken(SyntaxKind kind, int _ , string text)
{
Kind = kind;
Position = position;
Text = text;
}

Expand Down
17 changes: 16 additions & 1 deletion src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ public static class ExpressionToQueryConvertor
private static (Expression<Func<T, bool>> Expression, bool IsNested)? ConvertBinaryExpressionSyntaxToQuery<T>(
BinaryExpressionSyntax binarySyntax, IGridifyMapper<T> mapper)
{
var left = (binarySyntax.Left as FieldExpressionSyntax)?.FieldToken.Text.Trim();

var fieldExpression = binarySyntax.Left as FieldExpressionSyntax;

var left = fieldExpression?.FieldToken.Text.Trim();
var right = (binarySyntax.Right as ValueExpressionSyntax);
var op = binarySyntax.OperatorToken;

Expand All @@ -21,6 +24,9 @@ private static (Expression<Func<T, bool>> Expression, bool IsNested)? ConvertBin
var gMap = mapper.GetGMap(left);

if (gMap == null) return null;

if (fieldExpression!.IsCollection)
gMap.To = UpdateExpressionIndex(gMap.To,fieldExpression.Index);

if (gMap.IsNestedCollection)
{
Expand All @@ -36,6 +42,15 @@ private static (Expression<Func<T, bool>> Expression, bool IsNested)? ConvertBin
}
}

private static LambdaExpression UpdateExpressionIndex(LambdaExpression exp, int index)
{
var parameter = exp.Parameters[0];
var unary = exp.Body as UnaryExpression;
var body = unary!.Operand as MemberExpression;
var newBody = new PredicateBuilder.ReplaceExpressionVisitor(exp.Parameters[1],Expression.Constant(index,typeof(int))).Visit(body!);
return Expression.Lambda(newBody,parameter);
}

private static Expression<Func<T, bool>>? GenerateNestedExpression<T>(
IGridifyMapper<T> mapper,
IGMap<T> gMap,
Expand Down
22 changes: 11 additions & 11 deletions test/Gridify.Tests/GridifyExtensionsShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ public void ApplyFiltering_StartsWith()
.ApplyFiltering(gq)
.ToList();

var expected = _fakeRepository.Where(q => q.Name.StartsWith("A")).ToList();
var expected = _fakeRepository.Where(q => q.Name!.StartsWith("A")).ToList();

Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Expand Down Expand Up @@ -294,7 +294,7 @@ public void ApplyFiltering_NotStartsWith()
.ApplyFiltering(gq)
.ToList();

var expected = _fakeRepository.Where(q => !q.Name.StartsWith("A")).ToList();
var expected = _fakeRepository.Where(q => !q.Name!.StartsWith("A")).ToList();

Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Expand All @@ -310,7 +310,7 @@ public void ApplyFiltering_EndsWith()
.ApplyFiltering(gq)
.ToList();

var expected = _fakeRepository.Where(q => q.Name.EndsWith("li")).ToList();
var expected = _fakeRepository.Where(q => q.Name!.EndsWith("li")).ToList();
Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Assert.True(actual.Any());
Expand All @@ -325,7 +325,7 @@ public void ApplyFiltering_NotEndsWith()
.ApplyFiltering(gq)
.ToList();

var expected = _fakeRepository.Where(q => !q.Name.EndsWith("i")).ToList();
var expected = _fakeRepository.Where(q => !q.Name!.EndsWith("i")).ToList();

Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Expand All @@ -350,7 +350,7 @@ public void ApplyFiltering_ComplexWithParenthesis()
var actual = _fakeRepository.AsQueryable()
.ApplyFiltering("(name=*J|name=*S),(Id<5)")
.ToList();
var expected = _fakeRepository.Where(q => (q.Name.Contains("J") || q.Name.Contains("S")) && q.Id < 5).ToList();
var expected = _fakeRepository.Where(q => (q.Name!.Contains("J") || q.Name.Contains("S")) && q.Id < 5).ToList();
Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Assert.True(actual.Any());
Expand All @@ -364,7 +364,7 @@ public void ApplyFiltering_NestedParenthesisWithSpace()
var actual = _fakeRepository.AsQueryable()
.ApplyFiltering(gq)
.ToList();
var expected = _fakeRepository.Where(q => q.Name.Contains("J") || q.Name.Contains("S") && q.Id < 5).ToList();
var expected = _fakeRepository.Where(q => q.Name!.Contains("J") || q.Name.Contains("S") && q.Id < 5).ToList();
Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Assert.True(actual.Any());
Expand Down Expand Up @@ -394,13 +394,13 @@ public void ApplyFiltering_CaseInsensitiveSearchUsingConvertor() //issue #21
{
var gq = new GridifyQuery { Filter = "name=BOB" };
var gm = new GridifyMapper<TestClass>()
.AddMap("name", q => q.Name.ToLower(), c => c.ToLower());
.AddMap("name", q => q.Name!.ToLower(), c => c.ToLower());

var actual = _fakeRepository.AsQueryable()
.ApplyFiltering(gq, gm)
.ToList();

var expected = _fakeRepository.Where(q => q.Name.ToLower() == "BOB".ToLower()).ToList();
var expected = _fakeRepository.Where(q => q.Name!.ToLower() == "BOB".ToLower()).ToList();
Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Assert.True(actual.Any());
Expand All @@ -415,7 +415,7 @@ public void ApplyFiltering_CaseInsensitiveSearch() //issue #21
.ApplyFiltering(gq)
.ToList();

var expected = _fakeRepository.Where(q => q.Name.ToLower() == "BOB".ToLower()).ToList();
var expected = _fakeRepository.Where(q => q.Name!.ToLower() == "BOB".ToLower()).ToList();
Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Assert.True(actual.Any());
Expand Down Expand Up @@ -534,7 +534,7 @@ public void ApplyFiltering_NotEqual_ProcessingNullOrDefaultValue()
public void ApplyFiltering_Equal_ProcessingNullOrDefaultValueNonStringTypes()
{
var actual = _fakeRepository.AsQueryable().ApplyFiltering("myGuid=").ToList();
var expected = _fakeRepository.Where(q => q.MyGuid == null || q.MyGuid == default).ToList();
var expected = _fakeRepository.Where(q => q.MyGuid == default).ToList();

Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Expand All @@ -545,7 +545,7 @@ public void ApplyFiltering_Equal_ProcessingNullOrDefaultValueNonStringTypes()
public void ApplyFiltering_NotEqual_ProcessingNullOrDefaultValueNonStringTypes()
{
var actual = _fakeRepository.AsQueryable().ApplyFiltering("myGuid!=").ToList();
var expected = _fakeRepository.Where(q => q.MyGuid != null && q.MyGuid != default).ToList();
var expected = _fakeRepository.Where(q => q.MyGuid != default).ToList();

Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Expand Down
Loading

0 comments on commit eed8985

Please sign in to comment.