Skip to content

Commit

Permalink
add Client side Convertor
Browse files Browse the repository at this point in the history
  • Loading branch information
alirezanet committed Sep 17, 2020
1 parent 9009572 commit 24a00d6
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 71 deletions.
22 changes: 22 additions & 0 deletions src/Core/GMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Gridify
{
public class GMap<T>
{
public string From { get; set; }
public Expression<Func<T, object>> To { get; set; }
public Func<string, object> Convertor { get; set; }

public GMap(string from, Expression<Func<T, object>> to, Func<string, object> convertor = null)
{
From = from;
To = to;
Convertor = convertor;
}
}
}
44 changes: 26 additions & 18 deletions src/Core/GridifyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private static IGridifyQuery FixPagingData (this IGridifyQuery gridifyQuery)
return gridifyQuery;
}

private static Expression<Func<T, bool>> GetExpressionFromCondition<T> (string condition, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper)
private static Expression<Func<T, bool>> GetExpressionFromCondition<T> (string condition, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper)
{
try
{
Expand All @@ -43,15 +43,23 @@ private static Expression<Func<T, bool>> GetExpressionFromCondition<T> (string c
if (!maps.HasValue)
return null;

Expression<Func<T, object>> exp = mapper.Mappings[maps.Value.Left];
var gMap = mapper.GetGMap(maps.Value.Left);
Expression<Func<T, object>> exp = gMap.To;
var body = exp.Body;

// Remove the boxing for value types
if (body.NodeType == ExpressionType.Convert)
{
body = ((UnaryExpression) body).Operand;
}

object value = maps.Value.Right;

// execute user custom Convertor
if(gMap.Convertor != null)
value = gMap.Convertor.Invoke(maps.Value.Right);


if (value != null && body.Type != value.GetType ())
{
var converter = TypeDescriptor.GetConverter (body.Type);
Expand Down Expand Up @@ -133,7 +141,7 @@ private static Expression<Func<T, bool>> GetExpressionFromCondition<T> (string c
return complexFilters;
}

private static Expression<Func<T, bool>> GetExpressionFromInternalConditions<T> (IGridifyQuery gridifyQuery, GridifyMapper<T> mapper, List < (string condition, bool IsAnd) > conditions)
private static Expression<Func<T, bool>> GetExpressionFromInternalConditions<T> (IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper, List < (string condition, bool IsAnd) > conditions)
{
Expression<Func<T, bool>> finalExp = null;
bool nextOpIsAnd = true;
Expand Down Expand Up @@ -177,15 +185,15 @@ private static (string Left, string Operation, string Right) ? ParseFilter (stri
/// </summary>
/// <typeparam name="T">type to set mappings</typeparam>
/// <returns>returns an auto generated <c>GridifyMapper</c></returns>
public static GridifyMapper<T> GetDefaultMapper<T> () => new GridifyMapper<T> ().GenerateMappings ();
public static IGridifyMapper<T> GetDefaultMapper<T> () => new GridifyMapper<T> ().GenerateMappings ();

/// <summary>
/// if given mapper was null this function creates default generated mapper
/// </summary>
/// <param name="mapper">a <c>GridifyMapper<c/> that can be null</param>
/// <typeparam name="T">type to set mappings</typeparam>
/// <returns>return back mapper or new generated mapper if it was null</returns>
public static GridifyMapper<T> FixMapper<T> (this GridifyMapper<T> mapper) => mapper != null ? mapper : GetDefaultMapper<T> ();
public static IGridifyMapper<T> FixMapper<T> (this IGridifyMapper<T> mapper) => mapper != null ? mapper : GetDefaultMapper<T> ();

/// <summary>
/// adds Filtering,Ordering And Paging to the query
Expand All @@ -195,7 +203,7 @@ private static (string Left, string Operation, string Right) ? ParseFilter (stri
/// <param name="mapper">this is an optional parameter to apply filtering and ordering using a custom mapping configuration</param>
/// <typeparam name="T">type of target entity</typeparam>
/// <returns>returns user query after applying filtering, ordering and paging </returns>
public static IQueryable<T> ApplyEverything<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public static IQueryable<T> ApplyEverything<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
if (gridifyQuery == null) return query;
mapper = mapper.FixMapper ();
Expand All @@ -214,16 +222,16 @@ public static IQueryable<T> ApplyEverything<T> (this IQueryable<T> query, IGridi
/// <param name="mapper">this is an optional parameter to apply ordering using a custom mapping configuration</param>
/// <typeparam name="T">type of target entity</typeparam>
/// <returns>returns user query after applying Ordering </returns>
public static IQueryable<T> ApplyOrdering<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public static IQueryable<T> ApplyOrdering<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
if (gridifyQuery == null) return query;
mapper = mapper.FixMapper ();
if (String.IsNullOrWhiteSpace (gridifyQuery.SortBy) || !mapper.Mappings.ContainsKey (gridifyQuery.SortBy))
if (String.IsNullOrWhiteSpace (gridifyQuery.SortBy) || !mapper.HasMap (gridifyQuery.SortBy))
return query;
if (gridifyQuery.IsSortAsc)
return query.OrderBy (mapper.Mappings[gridifyQuery.SortBy]);
return query.OrderBy (mapper.GetExpression(gridifyQuery.SortBy));
else
return query.OrderByDescending (mapper.Mappings[gridifyQuery.SortBy]);
return query.OrderByDescending (mapper.GetExpression(gridifyQuery.SortBy));
}

/// <summary>
Expand All @@ -235,16 +243,16 @@ public static IQueryable<T> ApplyOrdering<T> (this IQueryable<T> query, IGridify
/// <param name="mapper">this is an optional parameter to apply ordering using a custom mapping configuration</param>
/// <typeparam name="T">type of target entity</typeparam>
/// <returns>returns user query after applying Ordering </returns>
public static IQueryable<T> ApplyOrdering<T, TKey> (this IQueryable<T> query, IGridifyQuery gridifyQuery, Expression<Func<T, TKey>> groupOrder, GridifyMapper<T> mapper = null)
public static IQueryable<T> ApplyOrdering<T, TKey> (this IQueryable<T> query, IGridifyQuery gridifyQuery, Expression<Func<T, TKey>> groupOrder, IGridifyMapper<T> mapper = null)
{
if (gridifyQuery == null) return query;
mapper = mapper.FixMapper ();
if (String.IsNullOrWhiteSpace (gridifyQuery.SortBy) || !mapper.Mappings.ContainsKey (gridifyQuery.SortBy))
if (String.IsNullOrWhiteSpace (gridifyQuery.SortBy) || !mapper.HasMap (gridifyQuery.SortBy))
return query;
if (gridifyQuery.IsSortAsc)
return query.OrderBy (groupOrder).ThenBy (mapper.Mappings[gridifyQuery.SortBy]);
return query.OrderBy (groupOrder).ThenBy (mapper.GetExpression(gridifyQuery.SortBy));
else
return query.OrderByDescending (groupOrder).ThenBy (mapper.Mappings[gridifyQuery.SortBy]);
return query.OrderByDescending (groupOrder).ThenBy (mapper.GetExpression(gridifyQuery.SortBy));
}

public static IQueryable<T> ApplyPaging<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery)
Expand All @@ -261,7 +269,7 @@ public static IQueryable<IGrouping<T2, T>> ApplyPaging<T, T2> (this IQueryable<I
return query.Skip ((gridifyQuery.Page - 1) * gridifyQuery.PageSize).Take (gridifyQuery.PageSize);
}

public static IQueryable<T> ApplyFiltering<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public static IQueryable<T> ApplyFiltering<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
if (gridifyQuery == null) return query;
if (String.IsNullOrWhiteSpace (gridifyQuery.Filter))
Expand Down Expand Up @@ -302,15 +310,15 @@ public static IQueryable<T> ApplyFiltering<T> (this IQueryable<T> query, IGridif
return query;
}

public static IQueryable<T> ApplyOrderingAndPaging<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public static IQueryable<T> ApplyOrderingAndPaging<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
mapper = mapper.FixMapper ();
query = query.ApplyOrdering (gridifyQuery, mapper);
query = query.ApplyPaging (gridifyQuery);
return query;
}

public static QueryablePaging<T> GridifyQueryable<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public static QueryablePaging<T> GridifyQueryable<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
mapper = mapper.FixMapper ();
query = query.ApplyFiltering (gridifyQuery, mapper);
Expand All @@ -332,7 +340,7 @@ public static QueryablePaging<T> GridifyQueryable<T> (this IQueryable<T> query,
/// <typeparam name="T">type of target entity</typeparam>
/// <returns>returns a loaded <c>Paging<T><c/> after applying filtering, ordering and paging </returns>
/// <returns></returns>
public static Paging<T> Gridify<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public static Paging<T> Gridify<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
mapper = mapper.FixMapper ();
var res = query.GridifyQueryable (gridifyQuery, mapper);
Expand Down
80 changes: 50 additions & 30 deletions src/Core/GridifyMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,79 @@ namespace Gridify
{
public class GridifyMapper<T> : IGridifyMapper<T>
{

public GridifyMapper (bool caseSensitive = false)
private HashSet<GMap<T>> _mappings;
public bool CaseSensitive { get; }
public GridifyMapper(bool caseSensitive = false)
{
CaseSensitive = caseSensitive;
Mappings = caseSensitive ? new Dictionary<string, Expression<Func<T, object>>> () :
new Dictionary<string, Expression<Func<T, object>>> (StringComparer.OrdinalIgnoreCase);
_mappings = new HashSet<GMap<T>>();
}
public Dictionary<string, Expression<Func<T, object>>> Mappings { get; set; }
public bool CaseSensitive { get; }
public GridifyMapper<T> GenerateMappings ()
public IGridifyMapper<T> GenerateMappings()
{
foreach (var item in typeof (T).GetProperties ())
foreach (var item in typeof(T).GetProperties())
{
var name = Char.ToLowerInvariant (item.Name[0]) + item.Name.Substring (1); //camel-case name
var name = Char.ToLowerInvariant(item.Name[0]) + item.Name.Substring(1); // camel-case name

// add to mapper object
Mappings.Add (name, GetExpression (item.Name));
_mappings.Add(new GMap<T>(name, CreateExpression(item.Name)));
}
return this;
}
public GridifyMapper<T> AddMap (string propertyName, Expression<Func<T, object>> column, bool replaceOldMapping = true)
public IGridifyMapper<T> AddMap(string from, Expression<Func<T, object>> to, Func<string, object> convertor = null, bool overrideIfExists = true)
{
if (Mappings.ContainsKey (propertyName))
{
if (replaceOldMapping)
{
RemoveMap (propertyName);
Mappings.Add (propertyName, column);
}
}
else
{
Mappings.Add (propertyName, column);
}
if (!overrideIfExists && HasMap(from))
throw new Exception($"Duplicate Key. the '{from}' key already exists");

RemoveMap(from);
_mappings.Add(new GMap<T>(from, to, convertor));
return this;
}

public IGridifyMapper<T> AddMap(GMap<T> gMap, bool overrideIfExists = true)
{
if (!overrideIfExists && HasMap(gMap.From))
throw new Exception($"Duplicate Key. the '{gMap.From}' key already exists");

RemoveMap(gMap.From);
_mappings.Add(gMap);
return this;
}

public GridifyMapper<T> RemoveMap (string propertyName)
public IGridifyMapper<T> RemoveMap(string from)
{
Mappings.Remove (propertyName);
_ = CaseSensitive ?
_mappings.RemoveWhere(q => from.Equals(q.From)) :
_mappings.RemoveWhere(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase));
return this;
}

private Expression<Func<T, object>> GetExpression (string propertyName)
public IGridifyMapper<T> RemoveMap(GMap<T> gMap)
{
_mappings.Remove(gMap);
return this;
}

public bool HasMap(string from) =>
CaseSensitive ?
_mappings.Any(q => q.From == from) : _mappings.Any(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase));

public GMap<T> GetGMap(string from) =>
CaseSensitive ?
_mappings.FirstOrDefault(q => from.Equals(q.From)) : _mappings.FirstOrDefault(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase));
public Expression<Func<T, object>> GetExpression(string key) =>
CaseSensitive ?
_mappings.FirstOrDefault(q => key.Equals(q.From)).To : _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase)).To;
private Expression<Func<T, object>> CreateExpression(string from)
{
// x =>
var parameter = Expression.Parameter (typeof (T));
var parameter = Expression.Parameter(typeof(T));
// x.Name
var mapProperty = Expression.Property (parameter, propertyName);
var mapProperty = Expression.Property(parameter, from);
// (object)x.Name
var convertedExpression = Expression.Convert (mapProperty, typeof (object));
var convertedExpression = Expression.Convert(mapProperty, typeof(object));
// x => (object)x.Name
return Expression.Lambda<Func<T, object>> (convertedExpression, parameter);
return Expression.Lambda<Func<T, object>>(convertedExpression, parameter);
}
public IEnumerable<GMap<T>> GetCurrentMaps() => _mappings.AsEnumerable();
}
}
16 changes: 10 additions & 6 deletions src/Core/IGridifyMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ namespace Gridify
{
public interface IGridifyMapper<T>
{
Dictionary<string, Expression<Func<T, object>>> Mappings { get; set; }
bool CaseSensitive { get; }

GridifyMapper<T> AddMap (string propertyName, Expression<Func<T, object>> column, bool replaceOldMapping = true);
GridifyMapper<T> GenerateMappings ();
GridifyMapper<T> RemoveMap (string propertyName);
IGridifyMapper<T> AddMap(string from, Expression<Func<T, object>> to, Func<string, object> convertor = null, bool overrideIfExists = true);
IGridifyMapper<T> AddMap(GMap<T> gMap, bool overrideIfExists = true);
IGridifyMapper<T> GenerateMappings();
IGridifyMapper<T> RemoveMap(string propertyName);
IGridifyMapper<T> RemoveMap(GMap<T> gMap);
Expression<Func<T, object>> GetExpression(string from);
GMap<T> GetGMap(string from);
bool HasMap(string key);
bool CaseSensitive {get;}
IEnumerable<GMap<T>> GetCurrentMaps();
}
}
4 changes: 2 additions & 2 deletions src/EntityFramework/GridifyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ public static partial class GridifyExtensions
{

#region "EntityFramework Integration"
public async static Task<QueryablePaging<T>> GridifyQueryableAsync<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper)
public async static Task<QueryablePaging<T>> GridifyQueryableAsync<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper)
{
query = query.ApplyFiltering (gridifyQuery, mapper);
var count = await query.CountAsync ();
query = query.ApplyOrdering (gridifyQuery, mapper);
query = query.ApplyPaging (gridifyQuery);
return new QueryablePaging<T> () { TotalItems = count, Query = query };
}
public async static Task<Paging<T>> GridifyAsync<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, GridifyMapper<T> mapper = null)
public async static Task<Paging<T>> GridifyAsync<T> (this IQueryable<T> query, IGridifyQuery gridifyQuery, IGridifyMapper<T> mapper = null)
{
mapper = mapper.FixMapper ();
var res = await query.GridifyQueryableAsync (gridifyQuery, mapper);
Expand Down
21 changes: 20 additions & 1 deletion test/Core.Tests/GridifyExtentionsShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ public void ApplyFiltering_UsingChildClassProperty()
Assert.True(actual.Any());
}

[Fact]
public void ApplyFiltering_CustomConvertor()
{
var gq = new GridifyQuery() { Filter = "name==liam" };
var gm = new GridifyMapper<TestClass>()
.GenerateMappings()
.AddMap("name", q => q.Name, q => q.ToUpper()); // useing client side Custom convertor

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

var expected = _fakeRepository.Where(q => q.Name == "LIAM").ToList();
Assert.Equal(expected.Count, actual.Count);
Assert.Equal(expected, actual);
Assert.True(actual.Any());
}

#endregion

#region "ApplyOrdering"
Expand Down Expand Up @@ -219,7 +237,7 @@ public void ApplyOrderingAndPaging_UsingCustomValues(short page, int pageSize, b
var skip = (page - 1) * pageSize;
var expectedQuery = _fakeRepository.AsQueryable();
if (isSortAsc)
expectedQuery = expectedQuery.OrderBy(q=>q.Name);
expectedQuery = expectedQuery.OrderBy(q => q.Name);
else
expectedQuery = expectedQuery.OrderByDescending(q => q.Name);
var expected = expectedQuery.Skip(skip).Take(pageSize).ToList();
Expand Down Expand Up @@ -254,6 +272,7 @@ private List<TestClass> GetSampleData()
lst.Add(new TestClass(19, "Pedram", null));
lst.Add(new TestClass(20, "Peyman", null));
lst.Add(new TestClass(21, "Fereshte", null));
lst.Add(new TestClass(22, "LIAM", null));
return lst;
}
#endregion
Expand Down
Loading

0 comments on commit 24a00d6

Please sign in to comment.