using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; namespace DD.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> 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 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(sortEnum.Current); while (sortEnum.MoveNext()) orderedQuery = orderedQuery.ThenSortBy(sortEnum.Current); return orderedQuery; } /// /// Добавить в запрос сортировку по возрастанию или убыванию. /// Этот метод сбросит ранее наложенные сортировки. /// /// /// /// /// Свойство сортировки. /// Состоит из названия свойства (в любом регистре) /// и опционально указания направления сортировки "asc" или "desc" /// /// /// var query = query("Date desc"); /// /// Запрос с примененной сортировкой public static IOrderedQueryable SortBy( this IQueryable 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; } /// /// Добавить в запрос дополнительную сортировку по возрастанию или убыванию. /// /// /// /// /// Свойство сортировки. /// Состоит из названия свойства (в любом регистре) /// и опционально указания направления сортировки "asc" или "desc" /// /// /// var query = query("Date desc"); /// /// Запрос с примененной сортировкой public static IOrderedQueryable ThenSortBy( this IOrderedQueryable 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; } /// /// Добавить в запрос сортировку по возрастанию или убыванию /// /// /// /// Название свойства (в любом регистре) /// Сортировать по убыванию /// Запрос с примененной сортировкой public static IOrderedQueryable SortBy( this IQueryable 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)genericMethod .Invoke(genericMethod, [query, lambdaExpression])!; return newQuery; } /// /// Добавить в запрос дополнительную сортировку по возрастанию или убыванию /// /// /// /// Название свойства (в любом регистре) /// Сортировать по убыванию /// Запрос с примененной сортировкой public static IOrderedQueryable ThenSortBy( this IOrderedQueryable 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)genericMethod .Invoke(genericMethod, [query, lambdaExpression])!; return newQuery; } }