mirror of
				https://github.com/Biarity/Sieve.git
				synced 2025-11-04 02:39:00 +01:00 
			
		
		
		
	Merge pull request #51 from Nekromancer/master
Allow filtering and sorting for nested objects
This commit is contained in:
		@@ -11,6 +11,8 @@ namespace Sieve.Attributes
 | 
				
			|||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public string Name { get; set; }
 | 
					        public string Name { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public string FullName => Name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public bool CanSort { get; set; }
 | 
					        public bool CanSort { get; set; }
 | 
				
			||||||
        public bool CanFilter { get; set; }
 | 
					        public bool CanFilter { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
	public interface ISievePropertyMetadata
 | 
						public interface ISievePropertyMetadata
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        string Name { get; set; }
 | 
					        string Name { get; set; }
 | 
				
			||||||
 | 
					        string FullName { get; }
 | 
				
			||||||
        bool CanFilter { get; set; }
 | 
					        bool CanFilter { get; set; }
 | 
				
			||||||
        bool CanSort { get; set; }
 | 
					        bool CanSort { get; set; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 = _fullName;
 | 
				
			||||||
                _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,11 +82,19 @@ 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 name,
 | 
				
			||||||
@@ -91,15 +102,17 @@ namespace Sieve.Services
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            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.Name.Equals(name, 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);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user