2024-11-26 10:23:48 +05:00
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
2024-12-16 15:38:46 +05:00
|
|
|
|
namespace DD.Persistence.Repository.Extensions;
|
2024-11-26 10:23:48 +05:00
|
|
|
|
|
|
|
|
|
public static class EFExtensionsSortBy
|
|
|
|
|
{
|
|
|
|
|
struct TypeAccessor
|
|
|
|
|
{
|
|
|
|
|
public LambdaExpression KeySelector { get; set; }
|
|
|
|
|
public MethodInfo OrderBy { get; set; }
|
|
|
|
|
public MethodInfo OrderByDescending { get; set; }
|
|
|
|
|
public MethodInfo ThenBy { get; set; }
|
|
|
|
|
public MethodInfo ThenByDescending { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ConcurrentDictionary<Type, Dictionary<string, TypeAccessor>> TypePropSelectors { get; set; } =
|
|
|
|
|
new();
|
|
|
|
|
|
|
|
|
|
private static readonly MethodInfo methodOrderBy = GetExtOrderMethod("OrderBy");
|
|
|
|
|
|
|
|
|
|
private static readonly MethodInfo methodOrderByDescending = GetExtOrderMethod("OrderByDescending");
|
|
|
|
|
|
|
|
|
|
private static readonly MethodInfo methodThenBy = GetExtOrderMethod("ThenBy");
|
|
|
|
|
|
|
|
|
|
private static readonly MethodInfo methodThenByDescending = GetExtOrderMethod("ThenByDescending");
|
|
|
|
|
|
|
|
|
|
private static MethodInfo GetExtOrderMethod(string methodName)
|
|
|
|
|
=> typeof(Queryable)
|
|
|
|
|
.GetMethods()
|
|
|
|
|
.Where(m => m.Name == methodName &&
|
|
|
|
|
m.IsGenericMethodDefinition &&
|
|
|
|
|
m.GetParameters().Length == 2 &&
|
|
|
|
|
m.GetParameters()[1].ParameterType.IsAssignableTo(typeof(LambdaExpression)))
|
|
|
|
|
.Single();
|
|
|
|
|
|
|
|
|
|
private static Dictionary<string, TypeAccessor> MakeTypeAccessors(Type type)
|
|
|
|
|
{
|
|
|
|
|
var propContainer = new Dictionary<string, TypeAccessor>();
|
|
|
|
|
var properties = type.GetProperties();
|
|
|
|
|
foreach (var propertyInfo in properties)
|
|
|
|
|
{
|
|
|
|
|
var name = propertyInfo.Name.ToLower();
|
|
|
|
|
ParameterExpression arg = Expression.Parameter(type, "x");
|
|
|
|
|
MemberExpression property = Expression.Property(arg, propertyInfo.Name);
|
2024-12-10 19:23:43 +05:00
|
|
|
|
var selector = Expression.Lambda(property, [arg]);
|
2024-11-26 10:23:48 +05:00
|
|
|
|
var typeAccessor = new TypeAccessor
|
|
|
|
|
{
|
|
|
|
|
KeySelector = selector,
|
|
|
|
|
OrderBy = methodOrderBy.MakeGenericMethod(type, propertyInfo.PropertyType),
|
|
|
|
|
OrderByDescending = methodOrderByDescending.MakeGenericMethod(type, propertyInfo.PropertyType),
|
|
|
|
|
ThenBy = methodThenBy.MakeGenericMethod(type, propertyInfo.PropertyType),
|
|
|
|
|
ThenByDescending = methodThenByDescending.MakeGenericMethod(type, propertyInfo.PropertyType),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
propContainer.Add(name, typeAccessor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return propContainer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Добавить в запрос сортировку по возрастанию или убыванию.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TSource"></typeparam>
|
|
|
|
|
/// <param name="query"></param>
|
|
|
|
|
/// <param name="propertySort">
|
|
|
|
|
/// Свойство сортировки.
|
|
|
|
|
/// Состоит из названия свойства (в любом регистре)
|
|
|
|
|
/// и опционально указания направления сортировки "asc" или "desc"
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <example>
|
|
|
|
|
/// var query = query("Date desc");
|
|
|
|
|
/// </example>
|
|
|
|
|
/// <returns>Запрос с примененной сортировкой</returns>
|
|
|
|
|
public static IOrderedQueryable<TSource> SortBy<TSource>(
|
|
|
|
|
this IQueryable<TSource> query,
|
|
|
|
|
IEnumerable<string> propertySorts)
|
|
|
|
|
{
|
|
|
|
|
if (propertySorts?.Any() != true)
|
|
|
|
|
return (IOrderedQueryable<TSource>)query;
|
|
|
|
|
|
|
|
|
|
var sortEnum = propertySorts.GetEnumerator();
|
|
|
|
|
sortEnum.MoveNext();
|
|
|
|
|
var orderedQuery = query.SortBy(sortEnum.Current);
|
|
|
|
|
|
|
|
|
|
while (sortEnum.MoveNext())
|
|
|
|
|
orderedQuery = orderedQuery.ThenSortBy(sortEnum.Current);
|
|
|
|
|
|
|
|
|
|
return orderedQuery;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Добавить в запрос сортировку по возрастанию или убыванию.
|
|
|
|
|
/// Этот метод сбросит ранее наложенные сортировки.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TSource"></typeparam>
|
|
|
|
|
/// <param name="query"></param>
|
|
|
|
|
/// <param name="propertySort">
|
|
|
|
|
/// Свойство сортировки.
|
|
|
|
|
/// Состоит из названия свойства (в любом регистре)
|
|
|
|
|
/// и опционально указания направления сортировки "asc" или "desc"
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <example>
|
|
|
|
|
/// var query = query("Date desc");
|
|
|
|
|
/// </example>
|
|
|
|
|
/// <returns>Запрос с примененной сортировкой</returns>
|
|
|
|
|
public static IOrderedQueryable<TSource> SortBy<TSource>(
|
|
|
|
|
this IQueryable<TSource> query,
|
|
|
|
|
string propertySort)
|
|
|
|
|
{
|
|
|
|
|
var parts = propertySort.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
var isDesc = parts.Length >= 2 && parts[1].ToLower().Trim() == "desc";
|
|
|
|
|
var propertyName = parts[0];
|
|
|
|
|
|
|
|
|
|
var newQuery = query.SortBy(propertyName, isDesc);
|
|
|
|
|
return newQuery;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Добавить в запрос дополнительную сортировку по возрастанию или убыванию.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TSource"></typeparam>
|
|
|
|
|
/// <param name="query"></param>
|
|
|
|
|
/// <param name="propertySort">
|
|
|
|
|
/// Свойство сортировки.
|
|
|
|
|
/// Состоит из названия свойства (в любом регистре)
|
|
|
|
|
/// и опционально указания направления сортировки "asc" или "desc"
|
|
|
|
|
/// </param>
|
|
|
|
|
/// <example>
|
|
|
|
|
/// var query = query("Date desc");
|
|
|
|
|
/// </example>
|
|
|
|
|
/// <returns>Запрос с примененной сортировкой</returns>
|
|
|
|
|
public static IOrderedQueryable<TSource> ThenSortBy<TSource>(
|
|
|
|
|
this IOrderedQueryable<TSource> query,
|
|
|
|
|
string propertySort)
|
|
|
|
|
{
|
|
|
|
|
var parts = propertySort.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
var isDesc = parts.Length >= 2 && parts[1].ToLower().Trim() == "desc";
|
|
|
|
|
var propertyName = parts[0];
|
|
|
|
|
|
|
|
|
|
var newQuery = query.ThenSortBy(propertyName, isDesc);
|
|
|
|
|
return newQuery;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Добавить в запрос сортировку по возрастанию или убыванию
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TSource"></typeparam>
|
|
|
|
|
/// <param name="query"></param>
|
|
|
|
|
/// <param name="propertyName">Название свойства (в любом регистре)</param>
|
|
|
|
|
/// <param name="isDesc">Сортировать по убыванию</param>
|
|
|
|
|
/// <returns>Запрос с примененной сортировкой</returns>
|
|
|
|
|
public static IOrderedQueryable<TSource> SortBy<TSource>(
|
|
|
|
|
this IQueryable<TSource> query,
|
|
|
|
|
string propertyName,
|
|
|
|
|
bool isDesc)
|
|
|
|
|
{
|
|
|
|
|
Type rootType = typeof(TSource);
|
|
|
|
|
var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors);
|
|
|
|
|
var propertyNameLower = propertyName.ToLower();
|
|
|
|
|
|
|
|
|
|
MethodInfo orderByDescending;
|
|
|
|
|
MethodInfo orderByAscending;
|
|
|
|
|
|
|
|
|
|
LambdaExpression? lambdaExpression = null;
|
|
|
|
|
|
|
|
|
|
if (propertyName.Contains('.'))
|
|
|
|
|
{
|
|
|
|
|
Type type = rootType;
|
|
|
|
|
ParameterExpression rootExpression = Expression.Parameter(rootType, "x");
|
|
|
|
|
Expression expr = rootExpression;
|
|
|
|
|
|
|
|
|
|
var propertyPath = propertyName.Split(".", StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < propertyPath.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
PropertyInfo pi = type.GetProperty(propertyPath[i])!;
|
|
|
|
|
expr = Expression.Property(expr, pi);
|
|
|
|
|
type = pi.PropertyType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Type delegateType = typeof(Func<,>).MakeGenericType(rootType, type);
|
|
|
|
|
lambdaExpression = Expression.Lambda(delegateType, expr, rootExpression);
|
|
|
|
|
|
|
|
|
|
orderByAscending = methodOrderBy.MakeGenericMethod(rootType, type);
|
|
|
|
|
orderByDescending = methodOrderByDescending.MakeGenericMethod(rootType, type);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var rootTypeAccessor = typePropSelector[propertyNameLower];
|
|
|
|
|
orderByAscending = rootTypeAccessor.OrderBy;
|
|
|
|
|
orderByDescending = rootTypeAccessor.OrderByDescending;
|
|
|
|
|
lambdaExpression = rootTypeAccessor.KeySelector;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var genericMethod = isDesc
|
|
|
|
|
? orderByDescending
|
|
|
|
|
: orderByAscending;
|
|
|
|
|
|
|
|
|
|
var newQuery = (IOrderedQueryable<TSource>)genericMethod
|
2024-12-10 19:23:43 +05:00
|
|
|
|
.Invoke(genericMethod, [query, lambdaExpression])!;
|
2024-11-26 10:23:48 +05:00
|
|
|
|
return newQuery;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Добавить в запрос дополнительную сортировку по возрастанию или убыванию
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="TSource"></typeparam>
|
|
|
|
|
/// <param name="query"></param>
|
|
|
|
|
/// <param name="propertyName">Название свойства (в любом регистре)</param>
|
|
|
|
|
/// <param name="isDesc">Сортировать по убыванию</param>
|
|
|
|
|
/// <returns>Запрос с примененной сортировкой</returns>
|
|
|
|
|
public static IOrderedQueryable<TSource> ThenSortBy<TSource>(
|
|
|
|
|
this IOrderedQueryable<TSource> query,
|
|
|
|
|
string propertyName,
|
|
|
|
|
bool isDesc)
|
|
|
|
|
{
|
|
|
|
|
Type rootType = typeof(TSource);
|
|
|
|
|
var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors);
|
|
|
|
|
var propertyNameLower = propertyName.ToLower();
|
|
|
|
|
|
|
|
|
|
MethodInfo orderByDescending;
|
|
|
|
|
MethodInfo orderByAscending;
|
|
|
|
|
|
|
|
|
|
LambdaExpression? lambdaExpression = null;
|
|
|
|
|
|
|
|
|
|
if (propertyName.Contains('.'))
|
|
|
|
|
{
|
|
|
|
|
Type type = rootType;
|
|
|
|
|
ParameterExpression rootExpression = Expression.Parameter(rootType, "x");
|
|
|
|
|
Expression expr = rootExpression;
|
|
|
|
|
|
|
|
|
|
var propertyPath = propertyName.Split(".", StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < propertyPath.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
PropertyInfo pi = type.GetProperty(propertyPath[i])!;
|
|
|
|
|
expr = Expression.Property(expr, pi);
|
|
|
|
|
type = pi.PropertyType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Type delegateType = typeof(Func<,>).MakeGenericType(rootType, type);
|
|
|
|
|
lambdaExpression = Expression.Lambda(delegateType, expr, rootExpression);
|
|
|
|
|
|
|
|
|
|
orderByAscending = methodThenBy.MakeGenericMethod(rootType, type);
|
|
|
|
|
orderByDescending = methodThenByDescending.MakeGenericMethod(rootType, type);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var rootTypeAccessor = typePropSelector[propertyNameLower];
|
|
|
|
|
orderByAscending = rootTypeAccessor.ThenBy;
|
|
|
|
|
orderByDescending = rootTypeAccessor.ThenByDescending;
|
|
|
|
|
lambdaExpression = rootTypeAccessor.KeySelector;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var genericMethod = isDesc
|
|
|
|
|
? orderByDescending
|
|
|
|
|
: orderByAscending;
|
|
|
|
|
|
|
|
|
|
var newQuery = (IOrderedQueryable<TSource>)genericMethod
|
2024-12-10 10:43:12 +05:00
|
|
|
|
.Invoke(genericMethod, [query, lambdaExpression])!;
|
2024-11-26 10:23:48 +05:00
|
|
|
|
return newQuery;
|
|
|
|
|
}
|
|
|
|
|
}
|