Comment appliquer OrderBy sur un IQueryable à l'aide d'un nom de colonne de chaîne dans une méthode d'extension générique?

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

Parce que le type pour OrderBy n'est pas déduit de sortExpression, je dois le spécifier quelque chose comme ça au moment de l'exécution:

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

Ou

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

Cependant, je ne pense pas que cela soit possible car TSortColumn ne peut être déterminé que pendant l'exécution.

Y a-t-il un moyen de contourner ceci?

JTew
la source
Je ne sais pas si c'est ce que vous recherchez, mais jetez un œil. Cheers
joaopintocruz
@JTew Comment puis-je implémenter un second ordre par clause..dire par id puis par date
SRJ

Réponses:

113

Nous avons fait quelque chose de similaire (pas 100% identique, mais similaire) dans un projet LINQ to SQL. Voici le 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);
}

Nous n'avons pas réellement utilisé de générique, nous avions une classe connue, mais cela devrait fonctionner sur un générique (j'ai mis l'espace réservé générique là où il devrait être).

Edit: Par ordre décroissant, passez au OrderByDescendinglieu de "OrderBy":

MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
Aaron Powell
la source
Heh no prob, je ne peux pas m'attribuer la réponse de toute façon :)
JTew
1
pour l'ordre décroissant, passez dans "OrderByDescending" au lieu de "OrderBy" MethodCallExpression resultExp = Expression.Call (typeof (Queryable), "OrderByDescending", ...
Garry English
3
Cela a très bien fonctionné, mais ce qui suit n'était qu'un très bon exemple de code propre: stackoverflow.com/questions/41244/dynamic-linq-orderby
BenSwayne
Powell Comment @ Aaron i mettre en œuvre un second ordre par id clause..say orderby puis par date
SRJ
3
À quoi sert le paramètre values?
Frank Fajardo
31

Vous pouvez également utiliser Dynamic Linq

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

Téléchargez C # ici http://msdn.microsoft.com/en-us/vcsharp/bb894665.aspx

Ensuite, ajoutez simplement le using Linq.Dynamic; et vous obtenez automatiquement 2 méthodes d'extension supplémentaires qui peuvent être utilisées comme ceci

return query.OrderBy("StringColumnName");
Jeremy Coenen
la source
Merci, j'avais vu Linq.Dynamic dans un échantillon sur le site de Phil Haack, mais je n'en étais pas sûr. Je vais jouer avec ça pendant le week-end.
JTew
Comme alternative, le Systems.Linq.Dynamic.dll peut être téléchargé à partir d'ici: github.com/kahanu/System.Linq.Dynamic
Baig
12

J'ai étendu vos fonctions pour ajouter la prise en charge des propriétés enfants.

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

Vous pouvez utiliser ces fonctions comme:

GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
Davy Landman
la source
1
Tu es mon héros !!
Sebastián Guerrero
1
je dois aimer les gens intelligents
Rod Johnson
J'ai essayé ce code et il fonctionne avec un enfant, mais pas avec plus d'un, par exemple, il fonctionne avec le tri sur x.String et x.Object.String, mais pas avec le tri sur x.Object.Object.String.
Robbert Raats
8

J'ai utilisé votre idée de méthode d'extension pour OrderBy. Mais en cas de "plusieurs à plusieurs", j'obtiens une erreur. Par exemple, vous avez la table Site, Customer et Customer_site. Pour un site donné, je veux trier par nom de client et dans l'extension OrderBy (lorsque je passe "site.customer" où le client est la propriété de navigation), j'obtiens une erreur en ligne: propertyAccess = Expression.MakeMemberAccess (propertyAccess, property);

C'est ce que j'utilise (avec quelques améliorations :-)):

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

Cordialement

Slobodan

Slobodan
la source
6

Il semble que ce soit la façon de le faire, maintenant pour vérifier que:

// ***** 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
la source
1
putain, 34 secondes derrière! : P
Aaron Powell
3

Si vous pouvez ajouter le package "System.Linq.Dynamic" alors, Trop facile sans aucune complication,

premier package insatll "System.Linq.Dynamic" du gestionnaire de packages NuGet, puis essayez comme ci-dessous selon vos besoins,

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

J'espère que cela aidera

dush88c
la source
2

J'ai corrigé un peu ce code: https://stackoverflow.com/a/1670085/5852630

Ce code fonctionne avec un tri séquentiel: exécutez d'abord "OrderBy", puis "ThenBy" (pas "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
la source
0

Voici mon adaptation de la réponse de @Davy Landman (je voulais une méthode d'extension) et j'ai simplifié un peu.

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

Il peut être utilisé comme ceci:

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