Allow filtering and sorting for nested objects

This commit is contained in:
Jakub Syty 2019-01-18 07:27:52 +01:00
parent 9174479624
commit bd904dff8a
5 changed files with 57 additions and 20 deletions

View File

@ -1,23 +1,34 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Sieve.Extensions
{
public static partial class LinqExtentions
{
public static IQueryable<TEntity> OrderByDynamic<TEntity>(this IQueryable<TEntity> source, string orderByProperty,
public static IQueryable<TEntity> OrderByDynamic<TEntity>(this IQueryable<TEntity> source, string fullPropertyName, PropertyInfo propertyInfo,
bool desc, bool useThenBy)
{
string command = desc ?
( useThenBy ? "ThenByDescending" : "OrderByDescending") :
( useThenBy ? "ThenBy" : "OrderBy");
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
dynamic propertyValue = parameter;
if (fullPropertyName.Contains("."))
{
var parts = fullPropertyName.Split('.');
for (var i = 0; i < parts.Length - 1; i++)
{
propertyValue = Expression.PropertyOrField(propertyValue, parts[i]);
}
}
var propertyAccess = Expression.MakeMemberAccess(propertyValue, propertyInfo);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, propertyInfo.PropertyType },
source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression);
}

View File

@ -3,6 +3,7 @@
public interface ISievePropertyMetadata
{
string Name { get; set; }
string FullName { get; set; }
bool CanFilter { get; set; }
bool CanSort { get; set; }
}

View File

@ -3,6 +3,7 @@
public class SievePropertyMetadata : ISievePropertyMetadata
{
public string Name { get; set; }
public string FullName { get; set; }
public bool CanFilter { get; set; }
public bool CanSort { get; set; }
}

View File

@ -176,11 +176,16 @@ namespace Sieve.Services
Expression innerExpression = null;
foreach (var filterTermName in filterTerm.Names)
{
var property = GetSieveProperty<TEntity>(false, true, filterTermName);
var (fullName, property) = GetSieveProperty<TEntity>(false, true, filterTermName);
if (property != null)
{
var converter = TypeDescriptor.GetConverter(property.PropertyType);
dynamic propertyValue = Expression.PropertyOrField(parameterExpression, property.Name);
dynamic propertyValue = parameterExpression;
foreach (var part in fullName.Split('.'))
{
propertyValue = Expression.PropertyOrField(propertyValue, part);
}
foreach (var filterTermValue in filterTerm.Values)
{
@ -300,11 +305,11 @@ namespace Sieve.Services
var useThenBy = false;
foreach (var sortTerm in model.GetSortsParsed())
{
var property = GetSieveProperty<TEntity>(true, false, sortTerm.Name);
var (fullName, property) = GetSieveProperty<TEntity>(true, false, sortTerm.Name);
if (property != null)
{
result = result.OrderByDynamic(property.Name, sortTerm.Descending, useThenBy);
result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy);
}
else
{
@ -345,13 +350,19 @@ namespace Sieve.Services
return mapper;
}
private PropertyInfo GetSieveProperty<TEntity>(
private (string, PropertyInfo) GetSieveProperty<TEntity>(
bool canSortRequired,
bool canFilterRequired,
string name)
{
return mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive)
?? FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
var property = mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
if(property.Item1 == null)
{
var prop = FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
return (prop.Name, prop);
}
return property;
}
private PropertyInfo FindPropertyBySieveAttribute<TEntity>(

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace Sieve.Services
{
@ -30,13 +31,14 @@ namespace Sieve.Services
public PropertyFluentApi(SievePropertyMapper sievePropertyMapper, Expression<Func<TEntity, object>> expression)
{
_sievePropertyMapper = sievePropertyMapper;
_property = GetPropertyInfo(expression);
(_fullName, _property) = GetPropertyInfo(expression);
_name = _property.Name;
_canFilter = false;
_canSort = false;
}
private string _name;
private readonly string _fullName;
private bool _canFilter;
private bool _canSort;
@ -66,12 +68,13 @@ namespace Sieve.Services
_sievePropertyMapper._map[typeof(TEntity)][_property] = new SievePropertyMetadata()
{
Name = _name,
FullName = _fullName,
CanFilter = _canFilter,
CanSort = _canSort
};
}
private static PropertyInfo GetPropertyInfo(Expression<Func<TEntity, object>> exp)
private static (string, PropertyInfo) GetPropertyInfo(Expression<Func<TEntity, object>> exp)
{
if (!(exp.Body is MemberExpression body))
{
@ -79,27 +82,37 @@ namespace Sieve.Services
body = ubody.Operand as MemberExpression;
}
return body?.Member as PropertyInfo;
var member = body?.Member as PropertyInfo;
var stack = new Stack<string>();
while (body != null)
{
stack.Push(body.Member.Name);
body = body.Expression as MemberExpression;
}
return (string.Join(".", stack.ToArray()), member);
}
}
public PropertyInfo FindProperty<TEntity>(
public (string, PropertyInfo) FindProperty<TEntity>(
bool canSortRequired,
bool canFilterRequired,
string name,
string fullName,
bool isCaseSensitive)
{
try
{
return _map[typeof(TEntity)]
var result = _map[typeof(TEntity)]
.FirstOrDefault(kv =>
kv.Value.Name.Equals(name, isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)
kv.Value.FullName.Equals(fullName, isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)
&& (canSortRequired ? kv.Value.CanSort : true)
&& (canFilterRequired ? kv.Value.CanFilter : true)).Key;
&& (canFilterRequired ? kv.Value.CanFilter : true));
return (result.Value.FullName, result.Key);
}
catch (Exception ex) when (ex is KeyNotFoundException || ex is ArgumentNullException)
{
return null;
return (null, null);
}
}
}