Merge pull request #51 from Nekromancer/master

Allow filtering and sorting for nested objects
This commit is contained in:
Biarity 2019-01-18 20:40:35 +10:00 committed by GitHub
commit 6413f70385
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 19 deletions

View File

@ -11,6 +11,8 @@ namespace Sieve.Attributes
/// </summary>
public string Name { get; set; }
public string FullName => Name;
public bool CanSort { get; set; }
public bool CanFilter { get; set; }
}

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; }
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);
_name = _property.Name;
(_fullName, _property) = GetPropertyInfo(expression);
_name = _fullName;
_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,11 +82,19 @@ 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,
@ -91,15 +102,17 @@ namespace Sieve.Services
{
try
{
return _map[typeof(TEntity)]
var result = _map[typeof(TEntity)]
.FirstOrDefault(kv =>
kv.Value.Name.Equals(name, 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);
}
}
}