Wie wende ich OrderBy auf ein IQueryable an, indem ich einen String-Spaltennamen innerhalb einer generischen Erweiterungsmethode verwende?

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

Da der Typ für OrderBy nicht von sortExpression abgeleitet ist, muss ich ihn zur Laufzeit wie folgt angeben:

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

Oder

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

Ich denke jedoch nicht, dass dies möglich ist, da TSortColumn nur zur Laufzeit ermittelt werden kann.

Gibt es einen Weg, dies zu umgehen?

JTew
quelle
Ich bin mir nicht sicher, ob dies das ist, wonach du suchst, aber schau es dir an. Cheers
Joaopintocruz
@JTew Wie kann ich eine zweite Bestellung nach Klausel implementieren? Sagen Sie die Reihenfolge nach ID und dann nach Datum
SRJ

Antworten:

113

Wir haben in einem LINQ to SQL-Projekt etwas Ähnliches (nicht 100% gleich, aber ähnlich) gemacht. Hier ist der Code:

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

Wir haben eigentlich kein Generikum verwendet, wir hatten eine bekannte Klasse, aber es sollte auf einem Generikum funktionieren (ich habe den generischen Platzhalter dort platziert, wo er sein sollte).

Bearbeiten: Bei absteigender Reihenfolge OrderByDescendinganstelle von "OrderBy" übergeben:

MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
Aaron Powell
quelle
Heh kein Problem, ich kann mir die Antwort sowieso nicht zuweisen :)
JTew
1
Für absteigende Reihenfolge übergeben Sie "OrderByDescending" anstelle von "OrderBy". MethodCallExpression resultExp = Expression.Call (typeof (Queryable), "OrderByDescending", ...
Garry English
3
Dies funktionierte gut, aber das Folgende war nur ein wirklich schönes sauberes Codebeispiel: stackoverflow.com/questions/41244/dynamic-linq-orderby
BenSwayne
@ Aaron Powell Wie kann ich eine zweite Bestellung nach Klausel implementieren? Sagen Sie die Reihenfolge nach ID und dann nach Datum
SRJ
3
Wofür ist der Parameter values?
Frank Fajardo
31

Sie können auch Dynamic Linq verwenden

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

C # hier herunterladen http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

Fügen Sie dann einfach die using Linq.Dynamic hinzu; und Sie erhalten automatisch 2 zusätzliche Erweiterungsmethoden, die wie folgt verwendet werden können

return query.OrderBy("StringColumnName");
Jeremy Coenen
quelle
Vielen Dank, ich hatte Linq.Dynamic in einem Beispiel bei Phil Haack gesehen, war mir aber nicht sicher. Ich werde über das Wochenende damit spielen.
JTew
Alternativ kann die Systems.Linq.Dynamic.dll von hier heruntergeladen werden: github.com/kahanu/System.Linq.Dynamic
Baig
12

Ich habe Ihre Funktionen erweitert, um Unterstützung für untergeordnete Eigenschaften hinzuzufügen.

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

Sie können diese Funktionen wie folgt verwenden:

GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
Davy Landman
quelle
1
Sie sind mein Held !!
Sebastián Guerrero
1
Ich muss kluge Leute lieben
Rod Johnson
Ich habe diesen Code ausprobiert und er funktioniert mit einem Kind, aber nicht mit mehr als einem, z. B. mit Sortieren nach x.String und x.Object.String, aber nicht mit Sortieren nach x.Object.Object.String.
Robbert Raats
8

Ich habe Ihre Idee für die Erweiterungsmethode für OrderBy verwendet. Aber im Fall von "vielen zu vielen" bekomme ich Fehler. Zum Beispiel haben Sie die Tabelle Site, Customer und Customer_site. Für eine bestimmte Site möchte ich nach dem Kundennamen und in der OrderBy-Erweiterung sortieren (wenn ich "site.customer" übergebe, wo der Kunde die Navigationseigenschaft ist), erhalte ich die folgende Fehlermeldung: propertyAccess = Expression.MakeMemberAccess (propertyAccess, property);

Dies ist, was ich benutze (mit einigen Verbesserungen :-)):

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

Grüße

Slobodan

Slobodan
quelle
6

Es scheint, dass dies der richtige Weg ist, um Folgendes zu überprüfen:

// ***** 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 *****
JTew
quelle
1
Verdammt, 34 Sekunden dahinter! : P
Aaron Powell
2

Wenn Sie das Paket "System.Linq.Dynamic" hinzufügen können, ist dies zu einfach und ohne Komplikationen.

Fisrt insatll Paket "System.Linq.Dynamic" von NuGet Paketmanager dann versuchen Sie wie unten, wie Sie benötigen,

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

Hoffe das wird helfen

dush88c
quelle
2

Ich habe diesen Code ein wenig korrigiert : https://stackoverflow.com/a/1670085/5852630

Dieser Code funktioniert mit sequentieller Sortierung: Führen Sie zuerst "OrderBy" und dann "ThenBy" aus (nicht "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;
}
SeroJah
quelle
0

Hier ist meine Adaption von @Davy Landmans Antwort (ich wollte eine Erweiterungsmethode) und ich habe sie ein wenig vereinfacht.

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

Es kann folgendermaßen verwendet werden:

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