2016-05-10 5 views
1

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.Convertir ce LINQ en arbre d'expression dynamique

Donc, si j'ai ces modèles:

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

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

je pouvais trier ma liste comme ceci:

var myList = db.MyModel.Where(m => m.IsActive); 
myList 
    .OrderBy(m => m.MyChildren 
     .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate); 

ou:

var myList = db.MyModel.Where(m => m.IsActive); 
myList.OrderBy(m => m.MyChildren.Max(c => c.SavedDate); 

Mais je veux être en mesure de tri dynamique, en fonction des options utilisateur. Je voudrais donc ceci:

var myList = db.MyModel.Where(m => m.IsActive); 
myList.OrderByField("MyChildren.SavedDate"); 

La méthode d'extension, je l'ai donc ressemble beaucoup à ceci:

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('.')) 
      { 
       var checkIfCollection = propertyGetter.Type.GetInterfaces()//(typeof (ICollection<>).FullName); 
        .Any(x => x.IsGenericType && 
         (x.GetGenericTypeDefinition() == typeof(ICollection<>) || x.GetGenericTypeDefinition() == typeof(IEnumerable<>))); 

       if (checkIfCollection) 
       { 
        var pgType = propertyGetter.Type; 
        var childType = pgType.GetGenericArguments().Single(); 
        var childProp = childType.GetProperty(propertyPart); 

        ParameterExpression childInParam = Expression.Parameter(childType, "c"); 
        var propertyAccess = Expression.Property(childInParam, childProp);      
        var orderByExp = Expression.Lambda(propertyAccess, childInParam); 
        // At this point, orderByExp is c => c.ActionDate 

        // Now I want to build the expression tree to handle the order by 
        XXXXX This is where I need help. 
       } 
       else 
       { 
        // This handles a singular property. Like "MyChildren.ChildName" 
        // and this part does work 
        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) 
      { 
       // This would be like 
       // list.OrderByDescending(m => m.MyChildren 
       //  .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate); 
       return list.OrderByDescending(getter); 
      } 
      else 
      { 
       // This would be like 
       // list.OrderBy(m => m.MyChildren 
       //  .OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate); 
       return list.OrderBy(getter); 
      } 
     } 

     return list; 
    } 
} 
+1

double possible de [dynamique LINQ OrderBy sur IEnumerable ] (http://stackoverflow.com/ questions/41244/dynamic-linq-orderby-on-ienumerablet) – ASh

+0

@Ash Je ne vois pas comment cela gère le tri sur une propriété d'une propriété de collection. –

Répondre

1

Fondamentalement, vous devez utiliser la surcharge Expression.Call suivante qui vous permet de construire une expression pour appeler statique générique méthodes (quelles sont toutes les méthodes d'extension LINQ).

Pour construire l'équivalent d'expression comme celui-ci

m => m.MyChildren.OrderByDescending(c => c.SavedDate).FirstOrDefault().SavedDate 

vous pouvez utiliser l'extrait suivant:

// At this point, orderByExp is c => c.ActionDate 
var orderByDescendingCall = Expression.Call(
    typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type }, 
    propertyGetter, orderByExp 
); 
var firstOrDefaultCall = Expression.Call(
    typeof(Enumerable), "FirstOrDefault", new Type[] { childType }, 
    orderByDescendingCall 
); 
propertyGetter = Expression.Property(firstOrDefaultCall, childProp); 

Mais notez que vous aurez NRE si la collection est vide.

Alors vous feriez mieux de construire une expression comme ceci:

m => m.MyChildren.OrderByDescending(c => c.SavedDate) 
    .Select(c => (DateTime?)c.SavedDate).FirstOrDefault() 

avec:

// At this point, orderByExp is c => c.ActionDate 
var orderByDescendingCall = Expression.Call(
    typeof(Enumerable), "OrderByDescending", new Type[] { childType, orderByExp.Body.Type }, 
    propertyGetter, orderByExp 
); 
Expression propertySelector = propertyAccess; 
// If value type property and not nullable, convert it to nullable 
if (propertySelector.Type.IsValueType && Nullable.GetUnderlyingType(propertySelector.Type) == null) 
    propertySelector = Expression.Convert(propertySelector, typeof(Nullable<>).MakeGenericType(propertySelector.Type)); 
var selectCall = Expression.Call(
    typeof(Enumerable), "Select", new Type[] { childType, propertySelector.Type }, 
    orderByDescendingCall, Expression.Lambda(propertySelector, childInParam) 
); 
propertyGetter = Expression.Call(
    typeof(Enumerable), "FirstOrDefault", new Type[] { propertySelector.Type }, 
    selectCall 
); 
+0

J'utilise LINQPad pour résoudre ce problème. Avec votre Expression.Call, je reçois le message 'Aucune méthode générique' OrderByDescending 'sur le type' System.Linq.Enumerable 'est compatible avec les arguments de type et les arguments fournis.' Ai-je manqué une clause d'utilisation? 'childType' et' orderByExp.Body.Type' sont tous deux 'DateTime'. 'propertyGetter.Type' est' System.Collections.Generic.ICollection'1 [PublicationSystem.Model.ActionItem] ' –

+0

' childType' doit être 'ChildModel'. Votre classe d'échantillon est incorrecte, 'MyChildren' devrait être' IEnumerable ', ou' ICollection 'ou' List 'afin de rendre vos exemples d'expression manuels compilés. –

+0

J'utilisais la mauvaise variable pour obtenir le type. Je fais des progrès maintenant. –