persistence/DD.Persistence.Repository/Extensions/EFExtensionsSortBy.cs
Roman Efremov ed6f33a84b
All checks were successful
Unit tests / test (push) Successful in 1m27s
Устранить дублирование EFExtensionsSortBy
2024-12-27 14:17:33 +05:00

229 lines
9.0 KiB
C#
Raw Permalink 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 DD.Persistence.Repository.Extensions;
public static class EFExtensionsSortBy
{
public 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");
public static Func<Type, Type?, TypeAccessor?, Tuple<MethodInfo, MethodInfo>> sortOrder =
(Type rootType, Type? type, TypeAccessor? accessor) =>
{
if (type is null && accessor.HasValue)
{
var accessorValue = accessor.Value;
return Tuple.Create(
accessorValue.OrderBy,
accessorValue.OrderByDescending
);
}
return Tuple.Create(
methodOrderBy.MakeGenericMethod(rootType, type!),
methodOrderByDescending.MakeGenericMethod(rootType, type!)
);
};
public static Func<Type, Type?, TypeAccessor?, Tuple<MethodInfo, MethodInfo>> thenSortOrder =
(Type rootType, Type? type, TypeAccessor? accessor) =>
{
if (type is null && accessor.HasValue)
{
var accessorValue = accessor.Value;
return Tuple.Create(
accessorValue.ThenBy,
accessorValue.ThenByDescending
);
}
return Tuple.Create(
methodThenBy.MakeGenericMethod(rootType, type!),
methodThenByDescending.MakeGenericMethod(rootType, type!)
);
};
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, [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(sortOrder, sortEnum.Current);
while (sortEnum.MoveNext())
orderedQuery = orderedQuery.SortBy(thenSortOrder, 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,
Func<Type, Type?, TypeAccessor?, Tuple<MethodInfo, MethodInfo>> orderMethod,
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(orderMethod, 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,
Func<Type, Type?, TypeAccessor?, Tuple<MethodInfo, MethodInfo>> orderMethod,
string propertyName,
bool isDesc)
{
Type rootType = typeof(TSource);
MethodInfo orderByDescending;
MethodInfo orderByAscending;
TypeAccessor? rootTypeAccessor = null;
Type? type = null;
LambdaExpression? lambdaExpression = null;
const string Separator = ".";
if (propertyName.Contains(Separator))
{
type = rootType;
ParameterExpression rootExpression = Expression.Parameter(rootType, "x");
Expression expr = rootExpression;
var propertyPath = propertyName.Split(Separator, 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);
Tuple<MethodInfo, MethodInfo> order = orderMethod
.Invoke(rootType, type, null);
orderByAscending = order.Item1;
orderByDescending = order.Item2;
}
else
{
var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors);
var propertyNameLower = propertyName.ToLower();
rootTypeAccessor = typePropSelector[propertyNameLower];
Tuple<MethodInfo, MethodInfo> order = orderMethod
.Invoke(rootType, type, rootTypeAccessor);
orderByAscending = order.Item1;
orderByDescending = order.Item2;
lambdaExpression = rootTypeAccessor.Value.KeySelector;
}
var genericMethod = isDesc
? orderByDescending
: orderByAscending;
var newQuery = (IOrderedQueryable<TSource>)genericMethod
.Invoke(genericMethod, [query, lambdaExpression])!;
return newQuery;
}
}