Как сделать сортировку по типу function(string param for sort,bool asc/desc)?

Подскажите как сделать сортировку , что б на вход я принимал свойство по какому буду сортировать и по какому типу буду сортировать т.е. по убиванию или возрастанию


Ответы (2 шт):

Автор решения: VladD

Ну например так.

static IOrderedQueryable<T> AddSort<T, V>(
            IQueryable<T> seq, Expression<Func<T, V>> fieldExtractor, bool ascending) =>
    ascending ? seq.OrderBy(fieldExtractor) : seq.OrderByDescending(fieldExtractor);

Пользоваться так:

IQueryable<Entity> entities = ...;
var sortedEntities = AddSort(entities, e => e.Weight, ascending: true);

Можно сделать из AddSort метод расширения.


Если вы хотите получить Expression из имени свойства, делайте, например, так:

static Expression<Func<TEntity, TValue>> CreateProjector<TEntity, TValue>(
        string propertyName)
{
    var entity = Expression.Parameter(typeof(TEntity), "e");
    var property = Expression.Property(entity, propertyName);
    var extractor = Expression.Lambda<Func<TEntity, TValue>>(property, entity);
    return extractor;
}

Соответственно пользоваться нужно так:

IQueryable<Entity> entities = ...;
var projector = CreateProjector<Entity, double>("Weight");
var sortedEntities = AddSort(entities, projector, ascending: true);

Если тип свойства заранее неизвестен, вы можете, например, скастить значение в double, но это приведёт к неоптимальному SQL. Более хитрое решение использует рефлексию, чтобы вызвать OrderBy нужными обобщёнными параметрами:

static IOrderedQueryable<TEntity> AddSortByPropertyName<TEntity>(
        IQueryable<TEntity> seq, string propertyName, bool ascending)
{
    var entityType = typeof(TEntity);

    // часть 1, строим expression как и раньше
    var entity = Expression.Parameter(entityType, "e");
    var property = Expression.Property(entity, propertyName);

    Type propertyType = property.Type;
    Type projectorType = typeof(Func<,>).MakeGenericType(entityType, propertyType);

    var extractor = Expression.Lambda(projectorType, property, entity);
    // в этой точке тип extractor'а нам известен лишь во время выполнения

    // часть 2, обобщённый вызов OrderBy
    var methodName = ascending ? nameof(Queryable.OrderBy) :
                                 nameof(Queryable.OrderByDescending);
    // нам нужен метод с такой вот сигнатурой
    // IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector
    // но это обобщённый метод, приходится искать его по имени и количеству параметров
    var genericMethodInfo =
        typeof(Queryable)
            .GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Single(mi => mi.Name == methodName && mi.GetParameters().Length == 2);
    // если Single вылетает, придётся строить более жёсткие условия
    // имея обобщённый метод, строим его конкретную инстанциацию
    var methodInfo = genericMethodInfo.MakeGenericMethod(entityType, propertyType);
    // и вызываем
    var result = methodInfo.Invoke(null, new object[] { seq, extractor });
    return (IOrderedQueryable<TEntity>)result;
}

Пользоваться так:

IQueryable<Entity> entities = ...;
var sortedEntities = AddSortByPropertyName(entities, "Weight", ascending: true);
→ Ссылка
Автор решения: A K

Используйте OrderBy и OrderByDescending как показано в учебнике от майкрософт:

public ActionResult Index(string sortOrder)
{
   ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
   ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
   var students = from s in db.Students
                  select s;
   switch (sortOrder)
   {
      case "name_desc":
         students = students.OrderByDescending(s => s.LastName);
         break;
      case "Date":
         students = students.OrderBy(s => s.EnrollmentDate);
         break;
      case "date_desc":
         students = students.OrderByDescending(s => s.EnrollmentDate);
         break;
      default:
         students = students.OrderBy(s => s.LastName);
         break;
   }
   return View(students.ToList());
}

В принципе, можно генерировать из строки лямбду используя рефлексию, как показано в этом вопросе: https://stackoverflow.com/q/31955025/5752652

Либо можно строить expression по заданным полям:

[UsedImplicitly]
public class Handler : BaseHandler, IRequestHandler<Query, IEnumerable<Contact>>
{
    public Handler(ApplicationDbContext applicationDbContext)
        : base(applicationDbContext)
    {
    }

    public async Task<IEnumerable<Contact>> Handle(Query request, CancellationToken cancellationToken)
    {
        var query = this.ApplicationDbContext.Contacts
                        .Include(x => x.ApplicationUser.City)
                        .Where(GetPredicate(request));

        Expression<Func<Contact, string>> orderBy = x => x.DisplayName;

        query = query.AddOrder(orderBy, request.Sort);

        var contacts = await query.AsNoTracking()
                                  .ToListAsync(cancellationToken);

        return contacts;
    }

    private static Expression<Func<Contact, bool>> GetPredicate(Query request)
    {
        Expression<Func<Contact, bool>> predicate = x => x.OwnerId == request.ManagerId;

        if (request.Filter.CityId != null)
        {
            Expression<Func<Contact, bool>> exprCity = x => x.ApplicationUser.CityId == request.Filter.CityId;
            predicate = predicate.AndAlso(exprCity);
        }

        ...

        return predicate;
    }
→ Ссылка