2010-07-23 13 views
3

J'ai une requête de IQueryable et je veux y appliquer un tri dynamiquement, le tri peut être sur plusieurs colonnes (asc ou desc). J'ai écrit la fonction générique suivante:LINQ: Tri dynamique sur plusieurs colonnes

private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order) 
    { 
     if (order == SortOrder.Ascending) 
     { 
      { 
       return query.OrderBy<T, U>(predicate); 
      } 
     } 
     else 
     { 
      { 
       return query.OrderByDescending<T, U>(predicate); 
      } 
     } 
    } 

SortOrder est mon simple, ENUM avec 2 valeurs: ascendant et descendant

J'appelle cette fonction dans une boucle, pour chaque colonne que l'utilisateur a demandé le tri. Cependant j'ai remarqué qu'il échoue parce qu'il trie toujours sur la dernière colonne utilisée, en ignorant les autres.

Je trouve qu'il ya une méthode « ThenBy » sur IOrderedQueryable si l'usage est valide:

var q = db.MyType.OrderBy(x=>x.Col1).ThenBy(y=>y.Col2); //etc. 

Mais comment puis-je faire générique? J'ai essayé de tester si la requête est IOrderedQueryable mais elle semble toujours être vraie même si c'est la plus simple var q = de x dans db.MyType select x

Je n'ai aucune idée pourquoi il a été conçu comme ceci. Quel est le problème avec:

var q = db.MyType.OrderBy(x=>x.Col1).OrderBy(y=>y.Col2); //etc. 

il est tellement intuitive

+0

Je suppose qu'ils ne laissent pas .OrderBy (.. Col1 ..). OrderBy (.. Col2 ..) la mouche est parce qu'ils sont pas de bonne façon de savoir si le résultat final est destiné à commander par Col1, puis réorganiser la liste entière par Col2; ou Col1 puis un sous-ordre de Col2 (comme vous le vouliez) –

+0

L'exécution de la requête est différée, lorsque vous écrivez .OrderBy rien ne se passe tant que vous n'avez pas parcouru l'ensemble de requêtes pour la première fois. – PawelRoman

Répondre

0

estimation totale, mais pouvez-vous faire quelque chose comme ça?

query.OrderBy(x => 1).ThenBy<T,U>(predicate) 

Les erreurs de syntaxe mises à part, l'idée est de faire une OrderBy() qui ne touche rien, alors faire le travail réel dans l'appel de méthode .ThenBy()

+0

Nice hack, et il devrait faire le travail, mais encore ce n'est qu'un hack. Il est difficile de croire que les auteurs de linq n'aient pas pensé à un tel scénario. Avec python/django faire de telles choses est si trivial ... – PawelRoman

1

Vous avez juste besoin de vérifier si la requête est déjà commandée:

private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order) 
{ 
    var ordered = query as IOrderedQueryable<T>; 
    if (order == SortOrder.Ascending) 
    { 
     if (ordered != null) 
      return ordered.ThenBy(predicate); 
     return query.OrderBy(predicate); 
    } 
    else 
    { 
     if (ordered != null) 
      return ordered.ThenByDescending(predicate); 
     return query.OrderByDescending(predicate); 
    } 
} 
+1

Cela ne marche pas.Comme je l'ai dit, la requête ALWAYS est de type IOrderedQueryable, event si c'est le plus simple de x dans y select x; – PawelRoman

0

J'écrirais un wrapper et j'utiliserais en interne des méthodes d'extension linq.

var resultList = presentList 
     .MyOrderBy(x => x.Something) 
     .MyOrderBY(y => y.SomethingElse) 
     .MyOrderByDesc(z => z.AnotherThing) 

public IQueryable<T> MyOrderBy(IQueryable<T> prevList, Expression<Func<T, U>> predicate) { 

     return (prevList is IOrderedQueryable<T>) 
      ? query.ThenBy(predicate) 
      : query.OrderBy(predicate); 
} 

public IQueryable<T> MyOrderByDesc(IQueryable<T> prevList, Expression<Func<T, U>> predicate) { 

     return (prevList is IOrderedQueryable<T>) 
      ? query.ThenByDescending(predicate) 
      : query.OrderByDescending(predicate); 
} 

PS: Je n'ai pas testé le code

+0

Si vous testez ce code, vous trouverez que IQueryable est toujours IOrderedQueryable , alors ThenBy fonction sera toujours appelée et OrderBy ne sera jamais appelée. Ceci est similaire à la façon dont j'ai essayé de l'implémenter avant d'écrire ici et j'ai trouvé cette surprise. – PawelRoman

0

Extension multi-ordre dynamique:

public static class DynamicExtentions 
{ 
    public static IEnumerable<T> DynamicOrder<T>(this IEnumerable<T> data, string[] orderings) where T : class 
    { 
     var orderedData = data.OrderBy(x => x.GetPropertyDynamic(orderings.First())); 
     foreach (var nextOrder in orderings.Skip(1)) 
     { 
      orderedData = orderedData.ThenBy(x => x.GetPropertyDynamic(nextOrder)); 
     } 
     return orderedData; 
    } 

    public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class 
    { 
     var param = Expression.Parameter(typeof(Tobj), "value"); 
     var getter = Expression.Property(param, propertyName); 
     var boxer = Expression.TypeAs(getter, typeof(object)); 
     var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();    
     return getPropValue(self); 
    } 
} 

Exemple:

var q =(myItemsToSort.Order(["Col1","Col2"]); 

Remarque: pas sûr tout fournisseur IQueryable peut traduire ceci

0

Que diriez-vous de faire le premier OrderBy statique, alors toujours ThenBy?

OrderColumn[] columnsToOrderby = getColumnsToOrderby(); 
IQueryable<T> data = getData(); 

if(!columnToOrderBy.Any()) { } 
else 
{ 
    OrderColumn firstColumn = columnsToOrderBy[0]; 
    IOrderedEnumerable<T> orderedData = 
     firstColumn.Ascending 
     ? data.OrderBy(predicate) 
     : data.OrderByDescending(predicate); 

    for (int i = 1; i < columnsToOrderBy.Length; i++) 
    { 
     OrderColumn column = columnsToOrderBy[i]; 
     orderedData = 
      column.Ascending 
      ? orderedData.ThenBy(predicate) 
      : orderedData.ThenByDescending(predicate); 
    } 
}