2016-05-09 4 views
0

J'ai une extension dans mon projet qui me permet de trier un IEnumerable avec une chaîne, de sorte que le tri peut être fait plus dynamiquement.Construire dynamiquement une arborescence d'expression pour le tri

Donc, si j'ai ces modèles:

public MyModel 
{ 
    public int Id {get; set;} 
    public string RecordName {get; set;} 
    public ChildModel MyChild {get; set;} 
} 

public ChildModel 
{ 
    public int ChildModelId {get; set;} 
    public DateTime SavedDate {get; set;} 
} 

je peux trier deux façons:

myList.OrderByField("RecordName "); 

myList.OrderByField("MyChild.SavedDate"); 

Cependant, si mon objet a une propriété ICollection, comme ICollection<ChildModel> MyChildren je peux coder en dur mon genre comme ceci:

myList 
    .OrderBy(m => m.MyChildren 
     .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate); 

Et obtenez ce que je veux.

Ma question est, comment puis-je mettre à jour ma méthode d'extension pour permettre d'obtenir les mêmes résultats avec ce:

myList.OrderByField("MyChildren.SavedDate"); 

Voici mon extension actuelle:

public static class MkpExtensions 
{ 
    public static IEnumerable<T> OrderByField<T>(this IEnumerable<T> list, string sortExpression) 
    { 
     sortExpression += ""; 
     string[] parts = sortExpression.Split(' '); 
     bool descending = false; 
     string fullProperty = ""; 

     if (parts.Length > 0 && parts[0] != "") 
     { 
      fullProperty = parts[0]; 

      if (parts.Length > 1) 
      { 
       descending = parts[1].ToLower().Contains("esc"); 
      } 

      ParameterExpression inputParameter = Expression.Parameter(typeof(T), "p"); 
      Expression propertyGetter = inputParameter; 
      foreach (string propertyPart in fullProperty.Split('.')) 
      { 
       PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart); 
       if (prop == null) 
        throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'"); 
       propertyGetter = Expression.Property(propertyGetter, prop); 
      } 

      Expression conversion = Expression.Convert(propertyGetter, typeof(object)); 
      var getter = Expression.Lambda<Func<T, object>>(conversion, inputParameter).Compile(); 

      if (descending) 
       return list.OrderByDescending(getter); 
      else 
       return list.OrderBy(getter); 
     } 

     return list; 
    } 
} 

je pensais de vérifier le type de prop et faire une déclaration if... else, mais je ne suis pas sûr.

Peut-être quelque chose comme ceci:

foreach (string propertyPart in fullProperty.Split('.')) 
{ 
    var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName); 
     .Any(x => x.IsGenericType && 
      (x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>))); 

    if (checkIfCollection) 
    { 
     // Can I get this to do something like 
     // myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate)); 

     // So far, I can get the propertyGetter type, and the type of the elements: 
     var pgType = propertyGetter.Type; 
     var childType = pgType.GetGenericArguments().Single(); 

     // Now I want to build the expression tree to get the max 
     Expression left = 
      Expression.Call(propertyGetter, pgType.GetMethod("Max", System.Type.EmptyTypes)); 
     // But pgType.GetMethod isn't working 
    } 
    else 
    { 
     PropertyInfo prop = propertyGetter.Type.GetProperty(propertyPart); 
     if (prop == null) 
      throw new Exception("No property '" + fullProperty + "' in + " + propertyGetter.Type.Name + "'"); 
     propertyGetter = Expression.Property(propertyGetter, prop); 
    } 
} 
+0

Pour votre information, vous pouvez simplifier la commande imbriquée codées en dur comme ceci: 'myList.OrderBy (m => m.MyChildren.Max (c => c.SavedDate)); En parlant de cela, quand votre programme voit '' MyChildren.SavedDate "', comment est-il supposé savoir trier ascendant par MyChildren, mais descendant par SavedDate? – StriplingWarrior

+0

Max travaillera pour les enregistrements enfants. Dans ma chaîne, j'utilise en fait 'MyChildren.SavedDate asc' ou' MyChildren.SavedDate desc' et je le change en conséquence. '.SavedDate' suppose le plus récent ... pour l'instant. ;) –

Répondre

0

La fonction Max est une méthode d'extension, pas une méthode membre de IEnumerable ou ICollection. Vous devez l'appeler de sa classe Enumerable.

Voici un exemple de comment appeler Max par arbre d'expression:

IEnumerable<int> list = new List<int> { 3, 5, 7, 2, 12, 1 }; 
var type = typeof(Enumerable); //This is the static class that contains Max 

//Find The overload of Max that matches the list 
var maxMethod = type.GetMethod("Max", new Type[] { typeof(IEnumerable<int>) }); 
ParameterExpression p = Expression.Parameter(typeof(IEnumerable<int>)); 

//Max is static, so the calling object is null 
var exp = Expression.Call(null, maxMethod, p); 
var lambda = Expression.Lambda<Func<IEnumerable<int>, int>>(exp, p); 
Console.WriteLine(lambda.Compile()(list));