2013-04-18 3 views
3

J'utilise des datatables dans plusieurs pages sur mon site ASP.NET MVC 3. Ils utilisent la pagination côté serveur et maintenant je veux implémenter le tri basé sur les en-têtes de colonne. Les données sont fournies avec iSortCol_0, qui est une valeur int de la colonne sur laquelle on clique.Get Dynamic OrderBy dans LINQ

Je n'ai pas aimé cette approche que la requête finirait par quelque chose comme:

if(iSortCol_0 == 0) 
{  
    // query 
    // OderBy(x => x.CarName) 
} 

Ensuite, ce sera répété pour chaque colonne (plus une clause else sur chaque à l'ordre par ordre décroissant). J'ai donc changé d'approche et passe maintenant le nom de la colonne au serveur.

Je suis venu avec ce qui suit:

Expression<Func<vw_Car, string>> sortExpression1 = null; 
Expression<Func<vw_Car, int>> sortExpression2 = null; 

switch(columnToSort) 
{ 
    case "InvoiceNo": sortExpression1 = x => x.CarNo; break; 
    case "ClientNumber": sortExpression1 = x => x.ForeName; break; 
    case "ClientName": sortExpression1 = x => x.SurName; break; 
    default: sortExpression2 = x => x.Age.Value; break; 
} 

// start of query 
.OrderByDir(sortDirection, sortExpression1 , sortExpression2) 

Maintenant, le OrderByDir ressemble ci-dessous:

public static IOrderedQueryable<T> OrderByDir<T>(this IQueryable<T> source, string dir, Expression<Func<T, string>> column1, Expression<Func<T, int>> column2) 
{ 
    if (column1 != null) 
    { 
     return dir == "asc" ? source.OrderBy(column1) : source.OrderByDescending(column1); 
    }   
    if (column2 != null) 
    { 
     return dir == "asc" ? source.OrderBy(column2) : source.OrderByDescending(column2); 
    }  
    return null; 
} 

Cela fonctionne en ce qu'elle trie les colonnes qui sont de type string ou int. J'ai une autre colonne de DateTime que je veux trier, donc je devrais écrire un autre sortExpression3 puis l'ajouter à mon OrderByDir.

Cependant, je ne aime pas vraiment la mise en œuvre - j'avais essayé d'écrire une expression générique de tri qui a un objet comme second paramètre au lieu de string, int, Datetime, mais quand a ce code en place, je recevais Impossible de convertir le type 'System.DateTime' en type 'System.Object'. LINQ to Entities ne prend en charge que les types primitifs de modèle de données d'entité de diffusion.

Quelqu'un at-il des idées quant à une meilleure approche possible à cela?

+0

Pourquoi deux sortExpressions? En fonction de votre code, l'expression un ou l'expression deux est définie mais jamais les deux en même temps. Est-ce correct? –

+0

@mattytommo: Non applicable, citation "Je suppose que vous avez seulement besoin de prendre en charge LINQ to Objects". –

+0

@DanielHilgarth - parce que l'une des expressions de tri est pour la colonne Âge (un champ int) - l'autre expression de tri est pour les colonnes de chaîne –

Répondre

7

Cette extension devrait à vos besoins:

public static class LinqExtensions 
{ 
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property) 
    { 
     return ApplyOrder(source, property, "OrderBy"); 
    } 

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property) 
    { 
     return ApplyOrder(source, property, "OrderByDescending"); 
    } 

    private static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName) 
    { 
     var props = property.Split('.'); 
     var type = typeof(T); 
     var arg = Expression.Parameter(type, "x"); 
     Expression expr = arg; 
     foreach (var prop in props) 
     { 
      // use reflection (not ComponentModel) to mirror LINQ 
      var pi = type.GetProperty(prop); 
      expr = Expression.Property(expr, pi); 
      type = pi.PropertyType; 
     } 
     var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type); 
     var lambda = Expression.Lambda(delegateType, expr, arg); 

     var result = typeof(Queryable).GetMethods().Single(
       method => method.Name == methodName 
         && method.IsGenericMethodDefinition 
         && method.GetGenericArguments().Length == 2 
         && method.GetParameters().Length == 2) 
       .MakeGenericMethod(typeof(T), type) 
       .Invoke(null, new object[] { source, lambda }); 
     return (IOrderedQueryable<T>)result; 
    } 
} 

exemple Invoke:

query.OrderBy("Age.Value"); 
+0

je recommanderais d'ajouter la direction de l'ordre comme paramètre à la place de la méthode séparée. – maxlego

+0

Cela peut être fait, mais l'indice ici est la construction et l'exécution de l'arbre d'expression lambda. D'autres changements secondaires sont une question de préférences subjectives. – jwaliszko