2016-05-10 5 views
1

私のプロジェクトでは、文字列でIEnumerableを並べ替えて並べ替えることで、より動的に並べ替えることができます。だから、このLINQを動的な式ツリーに変換する

、私はこれらのモデルがある場合:

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

か::

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

を私はこのように私のリストを並べ替えることができ

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

しかし、私のことができるようにしたいですユーザーオプションに基づいて動的に並べ替えます。だから私はこの希望:

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

を私が持っている拡張メソッドは、これまでのところ、次のようになります

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

可能な複製を(http://stackoverflow.com/質問/ 41244/dynamic-linq-orderby-on-ienumerablet) – ASh

+0

@Ashコレクションプロパティのプロパティをソートする方法が表示されません。 –

答えて

1

基本的には静的ジェネリックを呼び出すために、あなたが式を作成することができ、次のExpression.Callオーバーロードを使用する必要がありますメソッド(すべてのLINQ拡張メソッドは何ですか)。

// 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); 

しかしコレクションが空の場合は、NREを買ってあげることに注意してください:あなたは、次のスニペットを使用することができ、この

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

ような式と同等のものを構築するには

ですから、より良い、このような式構築したい:

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

と:[IEnumerableを 上の動的LINQのOrderBy]の

// 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

私はこれを動作させるためにLINQPadを使用しています。 Expression.Callを使用すると、 '' System.Linq.Enumerable '型の汎用的なメソッド' OrderByDescending 'は、指定された型引数および引数と互換性がありません。私はusing節がないのですか? 'childType'と' orderByExp.Body.Type'は両方とも 'DateTime'です。 'propertyGetter.Type'は' System.Collections.Generic.ICollection'1です。[PublicationSystem.Model.ActionItem] ' –

+0

' childType'は 'ChildModel'でなければなりません。あなたのサンプルクラスが正しくない場合、 'MyChildren'は' IEnumerable 'または' ICollection 'または' List 'でなければなりません。 –

+0

タイプを取得するために間違った変数を使用していました。私は今進歩しています。 –

関連する問題