2013-01-12 17 views
7

Sto cercando di creare un'espressione per l'ordinamento e ho scritto il codice che ordina il mio elenco utilizzando una proprietà.creare un'espressione con ordinamento multiplo

Ma ho bisogno di ordinarlo in primo luogo da una proprietà, in secondo luogo da un'altra proprietà e così via.

Voglio dire che voglio creare un'espressione che implementerà qualcosa del genere: students.OrderBy(fistExpression.Compile()).ThenBy(secondImpression.Complie()).ThenBy(thirdExpression.Compile()).

Quindi, come applicare dinamicamente i metodi ThenBy?

Ecco il mio codice:

Type studentType = typeof(Student); 
ParameterExpression studentParam = Expression.Parameter(studentType, "x"); 
MemberInfo ageProperty = studentType.GetProperty("Age"); 
MemberExpression valueInNameProperty = 
    Expression.MakeMemberAccess(studentParam, ageProperty); 
Expression<Func<Student, int>> orderByExpression = 
    Expression<Func<Student, int>>.Lambda<Func<Student, int>>(valueInNameProperty, studentParam); 
var sortedStudents = students.OrderBy(orderByExpression.Compile()); 
+0

Per chiarire: stai cercando un modo per creare una singola espressione che puoi trasmettere a .OrderBy che replicherà la funzionalità di passare una serie di espressioni a .OrderBy e .ThenBy, co rRect? –

+0

Dupe http://stackoverflow.com/questions/41244/dynamic-linq-orderby – aquinas

risposta

2

La mia soluzione:

public static Func<Student, object> BuildPredicate(string propertyName) 
{ 
    Type studentType = typeof(Student); 
    ParameterExpression studentParam = Expression.Parameter(studentType, "x"); 
    MemberInfo ageProperty = studentType.GetProperty(propertyName); 
    MemberExpression valueInNameProperty = Expression.MakeMemberAccess(studentParam, ageProperty); 
    UnaryExpression expression = Expression.Convert(valueInNameProperty, typeof (object)); 
    Expression<Func<Student, object>> orderByExpression = Expression.Lambda<Func<Student, object>>(expression, studentParam); 
    return orderByExpression.Compile(); 
} 

nella tua espressione rendere il codice viene aggiunto casting per object.

Ecco come è possibile creare una catena di ThenBy:

var sortedStudents = students.OrderBy(BuildPredicate("Age")); 
foreach (var property in typeof(Student).GetProperties().Where(x => !String.Equals(x.Name, "Age"))) 
{ 
    sortedStudents = sortedStudents.ThenBy(BuildPredicate(property.Name)); 
} 

var result = sortedStudents.ToList(); 

Infine, classe Student campione:

public class Student 
{ 
    public int Age { get; set; } 
    public string Name { get; set; } 
} 

Aggiornamento:

Un altro approccio sta usando gli attributi per contrassegnare offerte dal tuo Student per usarli in OrderBy e ThenBy. Come:

public class Student 
{ 
    [UseInOrderBy] 
    public int Age { get; set; } 

    [UseInOrderBy(Order = 1)] 
    public string Name { get; set; } 
} 

[AttributeUsage(AttributeTargets.Property)] 
internal class UseInOrderByAttribute : Attribute 
{ 
    public int Order { get; set; } 
} 

Ecco come si può costruire la catena di ordinamento utilizzando UseInOrderByAttribute:

Type studentType = typeof (Student); 
var properties = studentType.GetProperties() 
          .Select(x => new { Property = x, OrderAttribute = x.GetCustomAttribute<UseInOrderByAttribute>() }) 
          .Where(x => x.OrderAttribute != null) 
          .OrderBy(x => x.OrderAttribute.Order); 

var orderByProperty = properties.FirstOrDefault(x => x.OrderAttribute.Order == 0); 
if (orderByProperty == null) 
    throw new Exception(""); 

var sortedStudents = students.OrderBy(BuildPredicate(orderByProperty.Property.Name)); 
foreach (var property in properties.Where(x => x.Property.Name != orderByProperty.Property.Name)) 
{ 
    sortedStudents = sortedStudents.ThenBy(BuildPredicate(property.Property.Name)); 
} 

var result = sortedStudents.ToList(); 

Fix: BuildPredicate può essere Scritto senza dynamic. BuildPredicate codice di esempio modificato.

1

Suppongo che tu abbia proprietà private che vuoi essere in grado di ordinare. Se ad esempio si dispone di questa classe:

public class Student 
{ 
    public Student (int age, string name) 
    { 
     Age = age; 
     Name = name; 
    } 

    private string Name { get; set; } 

    public int Age { get; set; } 

    public override string ToString() 
    { 
     return string.Format ("[Student: Age={0}, Name={1}]", Age, Name); 
    } 
} 

È possibile utilizzare il seguente metodo per creare espressioni che otterranno entrambe le proprietà pubbliche e private:

public static Func<TType, TResult> CreateExpression<TType, TResult>(string propertyName) 
{ 
    Type type = typeof(TType); 
    ParameterExpression parameterExpression = Expression.Parameter(type, propertyName); 
    MemberInfo property = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
    MemberExpression valueInProperty = Expression.MakeMemberAccess(parameterExpression, property); 

    return Expression.Lambda<Func<TType,TResult>>(valueInProperty, parameterExpression).Compile(); 
} 

esempio di utilizzo:

 var students = new [] { 
      new Student(20, "Ben"), 
      new Student(20, "Ceasar"), 
      new Student(20, "Adam"), 
      new Student(21, "Adam"), 
     }; 

     var sortedStudents = students 
      .OrderBy(CreateExpression<Student, string>("Name")) 
      .ThenBy(CreateExpression<Student, int>("Age")); 

     sortedStudents.ToList().ForEach(student => Console.WriteLine(student)); 
     /* 
     Prints: 
     [Student: Age=20, Name=Adam] 
     [Student: Age=21, Name=Adam] 
     [Student: Age=20, Name=Ben] 
     [Student: Age=20, Name=Ceasar] 
     */