I have a set of extension methods that allow for using magic strings in the LINQ OrderBy()
methods. I know the first question will be why, but it's part of a generic repository and is there for flexibility so that strings can be sent from the UI and used directly.
I have it working if you pass in a magic string that represents a property on the main entity you are querying, but I'm having trouble making it more generic so it can handle multiple levels deep magic string. For example:
IQueryable<Contact> contacts = GetContacts();
contacts.OrderByProperty("Name"); // works great
// can't figure out how to handle this
contacts.OrderByProperty("ContactType.Name");
Here is the code that I have so far:
public static class LinqHelpers
{
private static readonly MethodInfo OrderByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderBy" && method.GetParameters().Length == 2);
private static readonly MethodInfo OrderByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "OrderByDescending" && method.GetParameters().Length == 2);
private static readonly MethodInfo ThenByMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "ThenBy" && method.GetParameters().Length == 2);
private static readonly MethodInfo ThenByDescendingMethod = typeof(Queryable).GetMethods().Single(method => method.Name == "ThenByDescending" && method.GetParameters().Length == 2);
public static IOrderedQueryable<TSource> ApplyOrdering<TSource>(IQueryable<TSource> source, string propertyName, MethodInfo orderingMethod)
{
var parameter = Expression.Parameter(typeof(TSource), "x");
var orderByProperty = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(orderByProperty, new[] { parameter });
var genericMethod = orderingMethod.MakeGenericMethod(new[] { typeof(TSource), orderByProperty.Type });
return (IOrderedQueryable<TSource>)genericMethod.Invoke(null, new object[] { source, lambda });
}
public static IOrderedQueryable<TSource> OrderByProperty<TSource>(this IQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, OrderByMethod);
}
public static IOrderedQueryable<TSource> OrderByDescendingProperty<TSource>(this IQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, OrderByDescendingMethod);
}
public static IOrderedQueryable<TSource> ThenByProperty<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, ThenByMethod);
}
public static IOrderedQueryable<TSource> ThenByDescendingProperty<TSource>(this IOrderedQueryable<TSource> source, string propertyName)
{
return ApplyOrdering(source, propertyName, ThenByDescendingMethod);
}
}
I'm pretty sure I need to split the propertyName
on the period and then use those parts to build up a more complicated Expression that involves a MemberExpression
and then a Property but I'm struggling. Any help or pointing in the right direction would be appreciated.
I wrote my own predicate builder type thing a while back. I attempted to adapt the code for posting here. This returns an expression to access a property, and can be used to build up more complicated expressions - just make sure that all the components of the expression use the exact same
param
object instance.This won't work as a drop in for your code. It'll will require some slight adaptations to make it work for your use I think.
This outputs
param => (param.Child.IntProperty == 42)
.You could use the
predicate
variable in a where clause. Let's say you had aList<Parent>
calledparents
, you could callparents.Where(predicate)
.