persistence/Persistence.Repository/Extensions/EFExtensionsSortBy.cs

267 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
namespace Persistence.Repository.Extensions;
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);
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
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
.Invoke(genericMethod, new object[] { query, lambdaExpression })!;
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;
// TODO: Устранить дублирование кода
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
.Invoke(genericMethod, new object[] { query, lambdaExpression })!;
return newQuery;
}
}