2013-01-12 10 views
7

Usiłuję zbudować wyrażenie sortowania i napisałem kod, który sortuje moją listę stosując jedną właściwość.zbudować wyrażenie z wieloma sortowania

Ale muszę uporządkować go najpierw przez jedną właściwość, po drugie przez inny obiekt i tak dalej.

Chodzi mi o to, że chcę zbudować wyrażenie, które zaimplementuje coś takiego: students.OrderBy(fistExpression.Compile()).ThenBy(secondImpression.Complie()).ThenBy(thirdExpression.Compile()).

Więc jak dynamicznie umieścić te metody ThenBy?

Oto mój kod:

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

dla wyjaśnienia - szukasz sposobu na zbudowanie pojedynczego wyrażenia można przekazać .OrderBy która będzie replikować funkcjonalność przechodząc serię wyrażeń .OrderBy i .ThenBy, CO prawda? –

+0

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

Odpowiedz

2

Moje rozwiązanie:

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

w swojej ekspresji kodu podejmowania jest dodany do odlewania object.

W ten sposób można stworzyć łańcuch 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(); 

Wreszcie klasy Student próbki:

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

Aktualizacja:

Innym podejściem jest użycie atrybutów oznaczyć z twojego Student, aby móc je wykorzystać w OrderBy i ThenBy. Jak:

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

W ten sposób można zbudować sortowania łańcuch używając 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 może być napisany bez dynamic. BuildPredicate przykładowy kod został zmieniony.

1

Zakładam, że masz prywatne właściwości, które chcesz sortować. Jeśli na przykład mieć tę klasę:

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

można użyć następującej metody budowania wyrażeń, które otrzymają zarówno właściwości publiczne i prywatne:

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

Przykład użycia:

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