From bd904dff8ad70952eafeaf89a9660adb71d92631 Mon Sep 17 00:00:00 2001 From: Jakub Syty Date: Fri, 18 Jan 2019 07:27:52 +0100 Subject: [PATCH] Allow filtering and sorting for nested objects --- Sieve/Extensions/OrderByDynamic.cs | 19 ++++++++++++---- Sieve/Models/ISievePropertyMetadata.cs | 1 + Sieve/Models/SievePropertyMetadata.cs | 1 + Sieve/Services/SieveProcessor.cs | 25 +++++++++++++++------ Sieve/Services/SievePropertyMapper.cs | 31 ++++++++++++++++++-------- 5 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Sieve/Extensions/OrderByDynamic.cs b/Sieve/Extensions/OrderByDynamic.cs index 414abe7..f014229 100644 --- a/Sieve/Extensions/OrderByDynamic.cs +++ b/Sieve/Extensions/OrderByDynamic.cs @@ -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 OrderByDynamic(this IQueryable source, string orderByProperty, + public static IQueryable OrderByDynamic(this IQueryable 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(resultExpression); } diff --git a/Sieve/Models/ISievePropertyMetadata.cs b/Sieve/Models/ISievePropertyMetadata.cs index 0a36e58..51b89b8 100644 --- a/Sieve/Models/ISievePropertyMetadata.cs +++ b/Sieve/Models/ISievePropertyMetadata.cs @@ -3,6 +3,7 @@ public interface ISievePropertyMetadata { string Name { get; set; } + string FullName { get; set; } bool CanFilter { get; set; } bool CanSort { get; set; } } diff --git a/Sieve/Models/SievePropertyMetadata.cs b/Sieve/Models/SievePropertyMetadata.cs index ae772b8..783f554 100644 --- a/Sieve/Models/SievePropertyMetadata.cs +++ b/Sieve/Models/SievePropertyMetadata.cs @@ -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; } } diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index b403adc..3d715cf 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -176,11 +176,16 @@ namespace Sieve.Services Expression innerExpression = null; foreach (var filterTermName in filterTerm.Names) { - var property = GetSieveProperty(false, true, filterTermName); + var (fullName, property) = GetSieveProperty(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(true, false, sortTerm.Name); + var (fullName, property) = GetSieveProperty(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( + private (string, PropertyInfo) GetSieveProperty( bool canSortRequired, bool canFilterRequired, string name) { - return mapper.FindProperty(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive) - ?? FindPropertyBySieveAttribute(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive); + var property = mapper.FindProperty(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive); + if(property.Item1 == null) + { + var prop = FindPropertyBySieveAttribute(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive); + return (prop.Name, prop); + } + return property; + } private PropertyInfo FindPropertyBySieveAttribute( diff --git a/Sieve/Services/SievePropertyMapper.cs b/Sieve/Services/SievePropertyMapper.cs index 3d91c6d..0f8a656 100644 --- a/Sieve/Services/SievePropertyMapper.cs +++ b/Sieve/Services/SievePropertyMapper.cs @@ -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> 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> exp) + private static (string, PropertyInfo) GetPropertyInfo(Expression> 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(); + while (body != null) + { + stack.Push(body.Member.Name); + body = body.Expression as MemberExpression; + } + + return (string.Join(".", stack.ToArray()), member); } } - public PropertyInfo FindProperty( + public (string, PropertyInfo) FindProperty( 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); } } }