mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-24 14:33:01 +01:00
Merge pull request #51 from Nekromancer/master
Allow filtering and sorting for nested objects
This commit is contained in:
commit
6413f70385
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user