How to Use a String to Create a Ef Order by Expression

Generate EF orderby expression by string

Using reflection and expression-trees you can provide the parameters and then call OrderBy function, Instead of returning Expression<Func<Task, T>> and then calling OrderBy.

Note that OrderBy is an extension method and has implemented in both System.Linq.Enumarable and System.Linq.Queryable classes. The first one is for linq-to-objects and the latter is for linq-to-entities. entity-framework needs the expression tree of the query in order to translate it to SQL commands. So we use the Queryable implementation.

It can be done by an extension method(explanations added as comments):

public static IOrderedQueryable<TSource> OrderBy<TSource>(
this IQueryable<TSource> query, string propertyName)
{
var entityType = typeof(TSource);

//Create x=>x.PropName
var propertyInfo = entityType.GetProperty(propertyName);
ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression property = Expression.Property(arg, propertyName);
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });

//Get System.Linq.Queryable.OrderBy() method.
var enumarableType = typeof(System.Linq.Queryable);
var method = enumarableType.GetMethods()
.Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
.Where(m =>
{
var parameters = m.GetParameters().ToList();
//Put more restriction here to ensure selecting the right overload
return parameters.Count == 2;//overload that has 2 parameters
}).Single();
//The linq's OrderBy<TSource, TKey> has two generic types, which provided here
MethodInfo genericMethod = method
.MakeGenericMethod(entityType, propertyInfo.PropertyType);

/*Call query.OrderBy(selector), with query and selector: x=> x.PropName
Note that we pass the selector as Expression to the method and we don't compile it.
By doing so EF can extract "order by" columns and generate SQL for it.*/
var newQuery = (IOrderedQueryable<TSource>)genericMethod
.Invoke(genericMethod, new object[] { query, selector });
return newQuery;
}

Now you can call this overload of OrderBy like any other overload of it.

For example:

var cheapestItems = _context.Items.OrderBy("Money").Take(10).ToList();

Which translates to:

SELECT TOP (10)  {coulmn names} FROM  [dbo].[Items] AS [Extent1] 
ORDER BY [Extent1].[Money] ASC

This approach can be used to define all overloads of OrderBy and OrderByDescending methods to have string property selector.

How to use a string to create a EF order by expression?

Creating an expression is not hard, but the tricky part is how to bind it to the corresponding OrderBy(Descending) / ThenBy(Descendig) methods when you don't know the type of the property (hence the type of the selector expression result).

Here is all that encapsulated in a custom extension method:

public static partial class QueryableExtensions
{
public static IOrderedQueryable<T> OrderByMember<T>(this IQueryable<T> source, string memberPath)
{
return source.OrderByMemberUsing(memberPath, "OrderBy");
}
public static IOrderedQueryable<T> OrderByMemberDescending<T>(this IQueryable<T> source, string memberPath)
{
return source.OrderByMemberUsing(memberPath, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenByMember<T>(this IOrderedQueryable<T> source, string memberPath)
{
return source.OrderByMemberUsing(memberPath, "ThenBy");
}
public static IOrderedQueryable<T> ThenByMemberDescending<T>(this IOrderedQueryable<T> source, string memberPath)
{
return source.OrderByMemberUsing(memberPath, "ThenByDescending");
}
private static IOrderedQueryable<T> OrderByMemberUsing<T>(this IQueryable<T> source, string memberPath, string method)
{
var parameter = Expression.Parameter(typeof(T), "item");
var member = memberPath.Split('.')
.Aggregate((Expression)parameter, Expression.PropertyOrField);
var keySelector = Expression.Lambda(member, parameter);
var methodCall = Expression.Call(
typeof(Queryable), method, new[] { parameter.Type, member.Type },
source.Expression, Expression.Quote(keySelector));
return (IOrderedQueryable<T>)source.Provider.CreateQuery(methodCall);
}

How to construct Order By Expression dynamically in Entity Framework?

I found a solution with the help of Jon Skeet's old answer.

public static class QueryHelper
{
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);

public static bool PropertyExists<T>(this IQueryable<T> source, string propertyName)
{
return typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) != null;
}

public static IQueryable<T> OrderByProperty<T>(
this IQueryable<T> source, string propertyName)
{
if (typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) == null)
{
return null;
}
ParameterExpression paramterExpression = Expression.Parameter(typeof (T));
Expression orderByProperty = Expression.Property(paramterExpression, propertyName);
LambdaExpression lambda = Expression.Lambda(orderByProperty, paramterExpression);
MethodInfo genericMethod =
OrderByMethod.MakeGenericMethod(typeof (T), orderByProperty.Type);
object ret = genericMethod.Invoke(null, new object[] {source, lambda});
return (IQueryable<T>) ret;
}

public static IQueryable<T> OrderByPropertyDescending<T>(
this IQueryable<T> source, string propertyName)
{
if (typeof (T).GetProperty(propertyName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance) == null)
{
return null;
}
ParameterExpression paramterExpression = Expression.Parameter(typeof (T));
Expression orderByProperty = Expression.Property(paramterExpression, propertyName);
LambdaExpression lambda = Expression.Lambda(orderByProperty, paramterExpression);
MethodInfo genericMethod =
OrderByDescendingMethod.MakeGenericMethod(typeof (T), orderByProperty.Type);
object ret = genericMethod.Invoke(null, new object[] {source, lambda});
return (IQueryable<T>) ret;
}
}

Usage

string orderBy = "Name";
if (query.PropertyExists(orderBy))
{
query = query.OrderByProperty(orderBy);
- OR -
query = query.OrderByPropertyDescending(orderBy);
}

Create LINQ to entities OrderBy expression on the fly

You basically can't use query expressions like this, due to the way they're translated. However, you can do it explicitly with extension methods:

string sortBy = HttpContext.Current.Request.QueryString["sidx"];
ParameterExpression prm = Expression.Parameter(typeof(buskerPosting), "posting");
Expression orderByProperty = Expression.Property(prm, sortBy);

// get the paged records
IQueryable<PostingListItemDto> query = be.buskerPosting
.Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
.OrderBy(orderByExpression)
.Select(posting => new PostingListItemDto { Set = posting })
.Skip<PostingListItemDto>((page - 1) * pageSize)
.Take<PostingListItemDto>(pageSize);

The tricky bit is getting the right expression tree type - that'll come in an edit :)

EDIT: The edit will be somewhat delayed for various reasons. Basically you may need to call a generic method using reflection, as Queryable.OrderBy needs a generic Expression<Func<TSource, TKey>> and although it looks like you know the source type at compile-time, you may not know the key type. If you do know it'll always be ordering by (say) an int, you can use:

Expression orderByProperty = Expression.Property(prm, sortBy);
var orderByExpression = Expression.Lambda<Func<buskerPosting, int>>
(orderByProperty, new[] { prm });

EDIT: Okay, it looks like I had time after all. Here's a short example of calling OrderBy using reflection:

using System;
using System.Reflection;
using System.Linq;
using System.Linq.Expressions;

public class Test
{
static void Main()
{
string[] names = { "Jon", "Holly", "Tom", "Robin", "Will" };
var query = names.AsQueryable();
query = CallOrderBy(query, "Length");
foreach (var name in query)
{
Console.WriteLine(name);
}
}

private static readonly MethodInfo OrderByMethod =
typeof(Queryable).GetMethods()
.Where(method => method.Name == "OrderBy")
.Where(method => method.GetParameters().Length == 2)
.Single();

public static IQueryable<TSource> CallOrderBy<TSource>
(IQueryable<TSource> source, string propertyName)
{
ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting");
Expression orderByProperty = Expression.Property(parameter, propertyName);

LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter });
Console.WriteLine(lambda);
MethodInfo genericMethod = OrderByMethod.MakeGenericMethod
(new[] { typeof(TSource), orderByProperty.Type });
object ret = genericMethod.Invoke(null, new object[] {source, lambda});
return (IQueryable<TSource>) ret;
}
}

You could easily refactor CallOrderBy into an extension method (e.g. OrderByProperty) like this:

public static class ReflectionQueryable
{
private static readonly MethodInfo OrderByMethod =
typeof(Queryable).GetMethods()
.Where(method => method.Name == "OrderBy")
.Where(method => method.GetParameters().Length == 2)
.Single();

public static IQueryable<TSource> OrderByProperty<TSource>
(this IQueryable<TSource> source, string propertyName)
{
ParameterExpression parameter = Expression.Parameter(typeof(TSource), "posting");
Expression orderByProperty = Expression.Property(parameter, propertyName);

LambdaExpression lambda = Expression.Lambda(orderByProperty, new[] { parameter });
Console.WriteLine(lambda);
MethodInfo genericMethod = OrderByMethod.MakeGenericMethod
(new[] { typeof(TSource), orderByProperty.Type });
object ret = genericMethod.Invoke(null, new object[] {source, lambda});
return (IQueryable<TSource>) ret;
}
}

Your original code then becomes:

string sortBy = HttpContext.Current.Request.QueryString["sidx"];
// get the paged records
IQueryable<PostingListItemDto> query = be.buskerPosting
.Where(posting => posting.buskerAccount.cmsMember.nodeId == m.Id)
.OrderByProperty(sortBy)
.Select(posting => new PostingListItemDto { Set = posting })
.Skip<PostingListItemDto>((page - 1) * pageSize)
.Take<PostingListItemDto>(pageSize);

(Apologies for the formatting involving horizontal scrollbars... I'll reformat later if anyone cares. Or you could do it for me if you have enough rep ;)

How can I do an OrderBy with a dynamic string parameter?

Absolutely. You can use the LINQ Dynamic Query Library, found on Scott Guthrie's blog. There's also an updated version available on CodePlex.

It lets you create OrderBy clauses, Where clauses, and just about everything else by passing in string parameters. It works great for creating generic code for sorting/filtering grids, etc.

var result = data
.Where(/* ... */)
.Select(/* ... */)
.OrderBy("Foo asc");

var query = DbContext.Data
.Where(/* ... */)
.Select(/* ... */)
.OrderBy("Foo ascending");

Call OrderBy() with a field name as a string

Thanks to all. Rob, your solution was pretty close to what I ended up with.

Based on your insights I did some more searching and came across Marc Gravel's answer here Dynamic LINQ OrderBy on IEnumerable<T> (second post).

It added dynamic's as an additional bonus.



Related Topics



Leave a reply



Submit