I am writing a list sorting extension method. My input is the list and a string with property name and sort direction. This string can have multiple properties like so: "Name ASC, Date DESC" etc.
I already implemented the string parsing and used reflection to get the property itself from the string, but what I am stuck on now is how do I dynamically chain the orderby methods.
something like:
_list.orderBy(x=>x.prop1).thenBy(x=>x.prop2)
etc.
Is there any way to build this dynamically?
Use reflection to get from the string property names to a PropertyInfo's. You can then build an expression tree using the PropertyInfo's to dynamically construct all the orderbys. Once you have the expression tree, compile it to a delegate, (say Func, IEnumerable>) Pass in your _list parameter to this delegate and it will give you the ordered result as another enumerable.
To get the reflection information for the generic method on Enumerable, have a look at the answer on this post: Get a generic method without using GetMethods
public static class Helper
{
public static IEnumerable<T> BuildOrderBys<T>(
this IEnumerable<T> source,
params SortDescription[] properties)
{
if (properties == null || properties.Length == 0) return source;
var typeOfT = typeof (T);
Type t = typeOfT;
IOrderedEnumerable<T> result = null;
var thenBy = false;
foreach (var item in properties
.Select(prop => new {PropertyInfo = t.GetProperty(prop.PropertyName), prop.Direction}))
{
var oExpr = Expression.Parameter(typeOfT, "o");
var propertyInfo = item.PropertyInfo;
var propertyType = propertyInfo.PropertyType;
var isAscending = item.Direction == ListSortDirection.Ascending;
if (thenBy)
{
var prevExpr = Expression.Parameter(typeof (IOrderedEnumerable<T>), "prevExpr");
var expr1 = Expression.Lambda<Func<IOrderedEnumerable<T>, IOrderedEnumerable<T>>>(
Expression.Call(
(isAscending ? thenByMethod : thenByDescendingMethod).MakeGenericMethod(typeOfT, propertyType),
prevExpr,
Expression.Lambda(
typeof (Func<,>).MakeGenericType(typeOfT, propertyType),
Expression.MakeMemberAccess(oExpr, propertyInfo),
oExpr)
),
prevExpr)
.Compile();
result = expr1(result);
}
else
{
var prevExpr = Expression.Parameter(typeof (IEnumerable<T>), "prevExpr");
var expr1 = Expression.Lambda<Func<IEnumerable<T>, IOrderedEnumerable<T>>>(
Expression.Call(
(isAscending ? orderByMethod : orderByDescendingMethod).MakeGenericMethod(typeOfT, propertyType),
prevExpr,
Expression.Lambda(
typeof (Func<,>).MakeGenericType(typeOfT, propertyType),
Expression.MakeMemberAccess(oExpr, propertyInfo),
oExpr)
),
prevExpr)
.Compile();
result = expr1(source);
thenBy = true;
}
}
return result;
}
private static MethodInfo orderByMethod =
MethodOf(() => Enumerable.OrderBy(default(IEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
private static MethodInfo orderByDescendingMethod =
MethodOf(() => Enumerable.OrderByDescending(default(IEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
private static MethodInfo thenByMethod =
MethodOf(() => Enumerable.ThenBy(default(IOrderedEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
private static MethodInfo thenByDescendingMethod =
MethodOf(() => Enumerable.ThenByDescending(default(IOrderedEnumerable<object>), default(Func<object, object>)))
.GetGenericMethodDefinition();
public static MethodInfo MethodOf<T>(Expression<Func<T>> method)
{
MethodCallExpression mce = (MethodCallExpression) method.Body;
MethodInfo mi = mce.Method;
return mi;
}
}
public static class Sample
{
private static void Main()
{
var data = new List<Customer>
{
new Customer {ID = 3, Name = "a"},
new Customer {ID = 3, Name = "c"},
new Customer {ID = 4},
new Customer {ID = 3, Name = "b"},
new Customer {ID = 2}
};
var result = data.BuildOrderBys(
new SortDescription("ID", ListSortDirection.Ascending),
new SortDescription("Name", ListSortDirection.Ascending)
).Dump();
}
}
public class Customer
{
public int ID { get; set; }
public string Name { get; set; }
}
The result of the sample as shown in LinqPad
I am not sure how you add the order by via reflection (too lazy to check) but here is the basic idea in kind of pseudocode:
var query = list.OrderBy(properties.First());
bool first = true;
foreach(var property in properties.Skip(1))
{
query = query.ThenBy(property);
}
You can use something like this:
var query = _list.OrderBy(x=>x.prop1);
if (shouldOrderByProp2Too)
query = query.ThenBy(x=>x.prop2);
if (shouldOrderByProp3Too)
query = query.ThenBy(x=>x.prop3);
// ...
// then use query the way you had your code
For your comment:
In this case I would go an implement IComparable
on the objects in your list and then just sort it / or use Compareres that dynamically check for this - the only other thing I can think of is doing this with reflection and dynamic-invoke ... not a thing you want to do if not really neccessary ;)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With