92 votes

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

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 de OrderBy n'est pas déduit de sortExpression-je besoin de le préciser quelque chose comme cela au moment de l'exécution:

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

Ou

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

Je ne pense pas que ce soit possible toutefois que TSortColumn peut seulement être déterminé au cours de l'exécution.

Est-il un moyen de contourner cela?

121voto

Aaron Powell Points 15598

Nous avons fait quelque chose de similaire (pas à 100% le même, mais similaire) dans une LINQ to SQL projet. 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 fait d'utiliser un générique, nous avons eu une catégorie connue, mais il devrait fonctionner sur un générique (j'ai mis le générique de l'espace réservé où il devrait être).

Edit: Par ordre décroissant, col en OrderByDescending au lieu de "OrderBy":

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

32voto

Jeremy Coenen Points 665

Vous pouvez également utiliser la Dynamique de Linq

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

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

Puis il suffit d'ajouter l'utilisation de Linq.Dynamique; et vous bénéficiez automatiquement de 2 autres méthodes d'extension qui peut être utilisée comme ceci

return query.OrderBy("StringColumnName");

14voto

Davy Landman Points 9010

Je l'ai étendu vos fonctions pour ajouter le support pour les 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);

8voto

Slobodan Points 41

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

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

Ce qui concerne

Slobodan

6voto

JTew Points 1599

Il semble que ce est 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 *****

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X