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;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
namespace Sieve.Extensions namespace Sieve.Extensions
{ {
public static partial class LinqExtentions 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) bool desc, bool useThenBy)
{ {
string command = desc ? string command = desc ?
( useThenBy ? "ThenByDescending" : "OrderByDescending") : ( useThenBy ? "ThenByDescending" : "OrderByDescending") :
( useThenBy ? "ThenBy" : "OrderBy"); ( useThenBy ? "ThenBy" : "OrderBy");
var type = typeof(TEntity); var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p"); 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 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)); source.Expression, Expression.Quote(orderByExpression));
return source.Provider.CreateQuery<TEntity>(resultExpression); return source.Provider.CreateQuery<TEntity>(resultExpression);
} }

View File

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

View File

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

View File

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

View File

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