Come si applica OrderBy su un IQueryable utilizzando un nome di colonna di stringa all'interno di un metodo di estensione generico?


85
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);
}

Poiché il tipo per OrderBy non è dedotto da sortExpression, devo specificarlo in questo modo in fase di esecuzione:

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

O

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

Non penso che ciò sia possibile, tuttavia, poiché TSortColumn può essere determinato solo durante il runtime.

C'è un modo per aggirare questo?


Non sono sicuro se questo quello che stai cercando, ma dare un'occhiata. Cheers
joaopintocruz

@JTew Come posso implementare un secondo ordine per clausola ... dì orderby id poi per data
SRJ

Risposte:


113

Abbiamo fatto qualcosa di simile (non uguale al 100%, ma simile) in un progetto LINQ to SQL. Ecco il codice:

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);
}

In realtà non abbiamo usato un generico, avevamo una classe nota, ma dovrebbe funzionare su un generico (ho messo il segnaposto generico dove dovrebbe essere).

Modifica: per l'ordine decrescente, passa OrderByDescendinginvece di "OrderBy":

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

Eh nessun problema, non posso comunque assegnare la risposta a me stesso :)
JTew

1
per l'ordine decrescente, passare "OrderByDescending" invece di "OrderBy" MethodCallExpression resultExp = Expression.Call (typeof (Queryable), "OrderByDescending", ...
Garry English

3
Questo ha funzionato bene, ma quanto segue è solo una bella esempio di codice pulito: stackoverflow.com/questions/41244/dynamic-linq-orderby
BenSwayne

@Aaron Powell Come posso implementare un secondo ordine per clausola ... dì orderby id poi per data
SRJ

3
A cosa serve il parametro values?
Frank Fajardo

31

Puoi anche usare Dynamic Linq

Informazioni qui http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

C # scarica qui http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

Quindi aggiungi semplicemente using Linq.Dynamic; e ottieni automaticamente 2 metodi di estensione aggiuntivi che possono essere utilizzati in questo modo

return query.OrderBy("StringColumnName");

Grazie, avevo visto Linq.Dynamic in un campione sul sito di Phil Haack ma non ne ero sicuro. Ci giocherò con questo durante il fine settimana.
JTew

In alternativa, è possibile scaricare Systems.Linq.Dynamic.dll
Baig

12

Ho esteso le tue funzioni per aggiungere il supporto per le proprietà figlio.

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;
}

Puoi usare queste funzioni come:

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

1
Tu sei il mio eroe !!
Sebastián Guerrero

1
devo amare le persone intelligenti
Rod Johnson

Ho provato questo codice e funziona con un bambino, ma non con più di uno, ad esempio funziona con l'ordinamento su x.String e x.Object.String, ma non con l'ordinamento su x.Object.Object.String.
Robbert Raats

8

Ho usato la tua idea per il metodo di estensione per OrderBy. Ma in caso di "molti a molti" ricevo un errore. Ad esempio, hai una tabella Site, Customer e Customer_site. Per un determinato sito desidero ordinare per nome del cliente e nell'estensione OrderBy (quando passo "site.customer" dove il cliente è la proprietà di navigazione) ottengo un errore nella riga: propertyAccess = Expression.MakeMemberAccess (propertyAccess, proprietà);

Questo è quello che uso (con alcuni miglioramenti :-)):

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;
}

Saluti

Slobodan


6

Sembra che questo sia il modo per farlo, ora per verificare che:

// ***** 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 *****

1
accidenti, 34 secondi dietro! : P
Aaron Powell

3

Se sei in grado di aggiungere il pacchetto "System.Linq.Dynamic", troppo facile senza complicazioni,

fisrt insatll pacchetto "System.Linq.Dynamic" dal gestore pacchetti NuGet, quindi prova come di seguito secondo le tue necessità,

Ex:

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);
            }
        }

Spero che questo ti aiuti


2

Ho corretto un po 'questo codice: https://stackoverflow.com/a/1670085/5852630

Questo codice funziona con l'ordinamento sequenziale: prima esegui "OrderBy", quindi "ThenBy" (non "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;
}

0

Ecco il mio adattamento dalla risposta di @Davy Landman (volevo un metodo di estensione) e ho semplificato un po '.

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);
    }

Può essere usato in questo modo:

gridview1.DataSource = DbContext.TB_CARS.SortBy("model", SortDirection.Descending);
//OR
gridview1.DataSource = DbContext.TB_CARS.SortBy("owner.first_name", 0);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.