Как применить OrderBy к IQueryable, используя имя строкового столбца в универсальном методе расширения?

public static IQueryable<TResult> ApplySortFilter<T, TResult>(this IQueryable<T> query, string columnName)
  where T : EntityObject
{
  var param = Expression.Parameter(typeof(T), "o");
  var body = Expression.PropertyOrField(param,columnName);

  var sortExpression = Expression.Lambda(body, param);
  return query.OrderBy(sortExpression);
}

Поскольку тип для OrderBy не выводится из sortExpression, мне нужно указать его примерно так во время выполнения:

var sortExpression = Expression.Lambda<T, TSortColumn>(body, param);

Или же

return query.OrderBy<T, TSortColumn>(sortExpression);

Однако я не думаю, что это возможно, поскольку TSortColumn можно определить только во время выполнения.

Это можно обойти?

Не уверен, что это то, что вы ищете, но посмотрите. Ваше здоровье

joaopintocruz 16.10.2012 21:33

@JTew Как я могу реализовать второй заказ по предложению .. скажите заказ по идентификатору, а затем по дате

SRJ 03.07.2014 17:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
86
2
86 883
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Кажется, что это - это способ сделать это, теперь, чтобы убедиться, что:

// ***** OrderBy(company => company) *****
// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
    typeof(Queryable),
    "OrderBy",
    new Type[] { queryableData.ElementType, queryableData.ElementType },
    whereCallExpression,
    Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****

блин, 34 секунды позади! :П

Aaron Powell 21.11.2008 05:01
Ответ принят как подходящий

Мы сделали нечто подобное (не на 100% то же самое, но похожее) в проекте LINQ to SQL. Вот код:

public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
    var type = typeof(T);
    var property = type.GetProperty(ordering);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExp = Expression.Lambda(propertyAccess, parameter);
    MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
    return source.Provider.CreateQuery<T>(resultExp);
}

На самом деле мы не использовали универсальный класс, у нас был известный класс, но он должен работать с универсальным (я поместил общий заполнитель там, где он должен быть).

Редактировать: В порядке убывания передайте OrderByDescending вместо OrderBy:

MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));

Хех нет проблем, я все равно не могу назначить себе ответ :)

JTew 21.11.2008 06:47

для нисходящего порядка передайте «OrderByDescending» вместо «OrderBy» MethodCallExpression resultExp = Expression.Call (typeof (Queryable), «OrderByDescending», ...

Garry English 26.07.2011 23:18

Это сработало отлично, но это был просто хороший пример чистого кода: stackoverflow.com/questions/41244/dynamic-linq-orderby

BenSwayne 29.06.2012 19:38

@Aaron Powell Как я могу реализовать второй заказ по предложению ... скажите заказ по идентификатору, а затем по дате

SRJ 03.07.2014 17:09

@SRJ, вы бы сделали это, используя метод ThenBy в LINQ, поэтому во второй последней строке будет "ThenBy", а не "OrderBy".

Aaron Powell 09.07.2014 07:56

Склонно ли это к внедрению кода любого вида (требуется ли проверка)?

MichaelD 03.11.2015 14:49

Для чего нужен параметр values?

Frank Fajardo 30.11.2015 02:54

Привет. я пытаюсь выполнить ту же задачу. Я пытался использовать ваш код, но у меня возникла одна проблема, например (System.Linq.Expressions.IArgumentProvider.ArgumentCount = '((System.Linq.Expressions.IArgumentProvider) resultExp) .Argu‌mentCount' вызвал исключение типа 'System .InvalidOperationException '), который выдает перед оператором возврата. Это исключение можно найти только в режиме отладки. Любая помощь, пожалуйста?

mrabaev48 07.06.2016 16:48

значения не используются

Guilherme Fidelis 16.02.2018 13:39

Я расширил ваши функции, добавив поддержку дочерних свойств.

private static LambdaExpression GenerateSelector<TEntity>(String propertyName, out Type resultType) where TEntity : class
{
    // Create a parameter to pass into the Lambda expression (Entity => Entity.OrderByField).
    var parameter = Expression.Parameter(typeof(TEntity), "Entity");
    //  create the selector part, but support child properties
    PropertyInfo property;
    Expression propertyAccess;
    if (propertyName.Contains('.'))
    {
            // support to be sorted on child fields.
            String[] childProperties = propertyName.Split('.');
            property = typeof(TEntity).GetProperty(childProperties[0]);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);
            for (int i = 1; i < childProperties.Length; i++)
            {
                    property = property.PropertyType.GetProperty(childProperties[i]);
                    propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
            }
    }
    else
    {
            property = typeof(TEntity).GetProperty(propertyName);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);
    }
    resultType = property.PropertyType;                     
    // Create the order by expression.
    return Expression.Lambda(propertyAccess, parameter);
}

private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable<TEntity> source, string methodName, String fieldName) where TEntity : class
{
    Type type = typeof(TEntity);
    Type selectorResultType;
    LambdaExpression selector = GenerateSelector<TEntity>(fieldName, out selectorResultType);
    MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                                    new Type[] { type, selectorResultType },
                                    source.Expression, Expression.Quote(selector));
    return resultExp;
}

Вы можете использовать эти функции, например:

GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);

Ты мой герой !!

Sebastián Guerrero 08.05.2016 02:14

должен любить умных людей

Rod Johnson 06.12.2016 17:33

Я пробовал этот код, и он работает с одним ребенком, но не более чем с одним, например он работает с сортировкой по x.String и x.Object.String, но не с сортировкой по x.Object.Object.String.

Robbert Raats 21.02.2019 12:02

Вы также можете использовать Dynamic Linq

Информация здесь http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

C# скачать здесь http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

Затем просто добавьте using Linq.Dynamic; и вы автоматически получаете 2 дополнительных метода расширения, которые можно использовать следующим образом

return query.OrderBy("StringColumnName");

Спасибо, я видел Linq.Dynamic в образце на сайте Фила Хаака, но не был в этом уверен. Я поиграю с этим на выходных.

JTew 15.05.2009 08:49

В качестве альтернативы Systems.Linq.Dynamic.dll можно загрузить отсюда: github.com/kahanu/System.Linq.Dynamic

Baig 28.03.2013 11:12

Я использовал вашу идею для метода расширения для OrderBy. Но в случае «многие ко многим» я получаю ошибку. Например, у вас есть таблица Site, Customer и Customer_site. Для данного сайта я хочу отсортировать по имени клиента и в расширении OrderBy (когда я передаю «site.customer», где клиент является свойством навигации), я получаю сообщение об ошибке в строке: propertyAccess = Expression.MakeMemberAccess (propertyAccess, property);

Вот что я использую (с некоторыми улучшениями :-)):

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
  IQueryable<TEntity> returnValue = null;

  string orderPair = orderByValues.Trim().Split(',')[0];
  string command = orderPair.ToUpper().Contains("DESC") ? "OrderByDescending" : "OrderBy";

  var type = typeof(TEntity);
  var parameter = Expression.Parameter(type, "p");

  string propertyName = (orderPair.Split(' ')[0]).Trim();

  System.Reflection.PropertyInfo property;
  MemberExpression propertyAccess;

  if (propertyName.Contains('.'))
  {
    // support to be sorted on child fields. 
    String[] childProperties = propertyName.Split('.');
    property = typeof(TEntity).GetProperty(childProperties[0]);
    propertyAccess = Expression.MakeMemberAccess(parameter, property);

    for (int i = 1; i < childProperties.Length; i++)
    {
      Type t = property.PropertyType;
      if (!t.IsGenericType)
      {
        property = t.GetProperty(childProperties[i]);
      }
      else
      {
        property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
      }

      propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
    }
  }
  else
  {
    property = type.GetProperty(propertyName);
    propertyAccess = Expression.MakeMemberAccess(parameter, property);
  }

  var orderByExpression = Expression.Lambda(propertyAccess, parameter);

  var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },

  source.Expression, Expression.Quote(orderByExpression));

  returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);

  if (orderByValues.Trim().Split(',').Count() > 1)
  {
    // remove first item
    string newSearchForWords = orderByValues.ToString().Remove(0, orderByValues.ToString().IndexOf(',') + 1);
    return source.OrderBy(newSearchForWords);
  }

  return returnValue;
}

С уважением

Слободан

Если вы можете добавить пакет "System.Linq.Dynamic", тогда Слишком просто без каких-либо осложнений,

первый пакет insatll "System.Linq.Dynamic" из диспетчера пакетов NuGet, затем попробуйте, как показано ниже, как вам нужно,

Бывший:

public IQueryable<TEntity> GetWithInclude(Expression<Func<TEntity, bool>> predicate,
                    List<string> sortBy, int pageNo, int pageSize = 12, params string[] include)
        {
            try
            {
                var numberOfRecordsToSkip = pageNo * pageSize;
                var dynamic = DbSet.AsQueryable();

                foreach (var s in include)
                {
                    dynamic.Include(s);
                }
                 return dynamic.OrderBy("CreatedDate").Skip(numberOfRecordsToSkip).Take(pageSize);


            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
        }

Надеюсь, это поможет

Я немного поправил этот код: https://stackoverflow.com/a/1670085/5852630

Этот код работает с последовательной сортировкой: сначала выполните «OrderBy», затем «ThenBy» (не «OrderBy»!)

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByValues) where TEntity : class
{
    IQueryable<TEntity> returnValue = null;

    string[] orderPairs = orderByValues.Trim().Split(',');

    Expression resultExpression = source.Expression;

    string strAsc = "OrderBy";
    string strDesc = "OrderByDescending";

    foreach (string orderPair in orderPairs)
    {
        if (string.IsNullOrWhiteSpace(orderPair))
            continue;

        string[] orderPairArr = orderPair.Trim().Split(' ');

        string propertyName = orderPairArr[0].Trim();
        string orderNarrow = orderPairArr.Length > 1 ? orderPairArr[1].Trim() : string.Empty;

        string command = orderNarrow.ToUpper().Contains("DESC") ? strDesc : strAsc;

        Type type = typeof(TEntity);
        ParameterExpression parameter = Expression.Parameter(type, "p");

        System.Reflection.PropertyInfo property;
        Expression propertyAccess;

        if (propertyName.Contains('.'))
        {
            // support to be sorted on child fields. 
            String[] childProperties = propertyName.Split('.');
            property = typeof(TEntity).GetProperty(childProperties[0]);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);

            for (int i = 1; i < childProperties.Length; i++)
            {
                Type t = property.PropertyType;
                if (!t.IsGenericType)
                {
                    property = t.GetProperty(childProperties[i]);
                }
                else
                {
                    property = t.GetGenericArguments().First().GetProperty(childProperties[i]);
                }

                propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
            }
        }
        else
        {
            property = type.GetProperty(propertyName);
            propertyAccess = Expression.MakeMemberAccess(parameter, property);
        }

        if (property.PropertyType == typeof(object))
        {
            propertyAccess = Expression.Call(propertyAccess, "ToString", null);
        }

        LambdaExpression orderByExpression = Expression.Lambda(propertyAccess, parameter);

        resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType == typeof(object) ? typeof(string) : property.PropertyType },
            resultExpression, Expression.Quote(orderByExpression));

        strAsc = "ThenBy";
        strDesc = "ThenByDescending";
    }

    returnValue = source.Provider.CreateQuery<TEntity>(resultExpression);

    return returnValue;
}

Вот моя адаптация из ответа @ Дэви Лэндман (мне нужен метод расширения), и я немного упростил.

public static IQueryable<T> SortBy<T>(this IQueryable<T> source, 
                                      String propertyName, 
                                      WebControls.SortDirection direction)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (String.IsNullOrEmpty(propertyName)) return source;

        // Create a parameter to pass into the Lambda expression
        //(Entity => Entity.OrderByField).
        var parameter = Expression.Parameter(typeof(T), "Entity");

        //  create the selector part, but support child properties (it works without . too)
        String[] childProperties = propertyName.Split('.');
        MemberExpression property = Expression.Property(parameter, childProperties[0]);
        for (int i = 1; i < childProperties.Length; i++)
        {
            property = Expression.Property(property, childProperties[i]);
        }

        LambdaExpression selector = Expression.Lambda(property, parameter);

        string methodName = (direction > 0) ? "OrderByDescending" : "OrderBy";

        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                                        new Type[] { source.ElementType, property.Type },
                                        source.Expression, Expression.Quote(selector));

        return source.Provider.CreateQuery<T>(resultExp);
    }

Его можно использовать так:

gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);

Другие вопросы по теме