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> 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> 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> 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 MakeTypeAccessors(Type type) { var propContainer = new Dictionary(); 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; } /// /// Добавить в запрос сортировку по возрастанию или убыванию. /// /// /// /// /// Свойство сортировки. /// Состоит из названия свойства (в любом регистре) /// и опционально указания направления сортировки "asc" или "desc" /// /// /// var query = query("Date desc"); /// /// Запрос с примененной сортировкой public static IOrderedQueryable SortBy( this IQueryable query, IEnumerable propertySorts) { if (propertySorts?.Any() != true) return (IOrderedQueryable)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; } /// /// Добавить в запрос сортировку по возрастанию или убыванию. /// Этот метод сбросит ранее наложенные сортировки. /// /// /// /// /// Свойство сортировки. /// Состоит из названия свойства (в любом регистре) /// и опционально указания направления сортировки "asc" или "desc" /// /// /// var query = query("Date desc"); /// /// Запрос с примененной сортировкой public static IOrderedQueryable SortBy( this IQueryable query, Func> 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; } /// /// Добавить в запрос сортировку по возрастанию или убыванию /// /// /// /// Название свойства (в любом регистре) /// Сортировать по убыванию /// Запрос с примененной сортировкой public static IOrderedQueryable SortBy( this IQueryable query, Func> 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 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 order = orderMethod .Invoke(rootType, type, rootTypeAccessor); orderByAscending = order.Item1; orderByDescending = order.Item2; lambdaExpression = rootTypeAccessor.Value.KeySelector; } var genericMethod = isDesc ? orderByDescending : orderByAscending; var newQuery = (IOrderedQueryable)genericMethod .Invoke(genericMethod, [query, lambdaExpression])!; return newQuery; } }