Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building an OrderBy Lambda expression based on child entity's property

I'm trying to generate a LINQ OrderBy clause using lambda expressions with an input of the column name of an entity as a string (in the "sortOn" variable below).

The code below works fine for a sortOn value like "Code" generating the lambda

p => p.Code

But I would also like to sort on a child entity, where the lambda might be

p => p.Category.Description

So in this instance, I would just like to set sortOn = "Category.Description" and have the correct lamdba expression generated.

Is this possible? Any suggestions about the best way to do this would be welcomed.

This code works fine for the simple case:

var param = Expression.Parameter(typeof (Product), "p");

var sortExpression = Expression.Lambda<Func<Product, object>>(
    Expression.Property(param, sortOn), param);

if (sortAscending ?? true)
{
   products = products.OrderBy(sortExpression);
}
else
{
   products = products.OrderByDescending(sortExpression);
}

The use-case for this problem is displaying a grid of data and being able to sort the data, simply by passing the column name to be sorted on back to the server. I'd like to make the solution generic, but have started using a particular type (Product in the example) for now.

like image 691
Appetere Avatar asked Jul 13 '12 16:07

Appetere


1 Answers

Here is an extension OrderBy method which works for any number of nested parameters.

public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string key, bool asc = true)
{
  try
  {
    string orderMethodName = asc ? "OrderBy" : "OrderByDescending";
    Type type = typeof(T);
    Type propertyType = type.GetProperty(key)?.PropertyType; ;

    var param = Expression.Parameter(type, "x");
    Expression parent = param;

    var keyParts = key.Split('.');
    for (int i = 0; i < keyParts.Length; i++)
    {
      var keyPart = keyParts[i];
      parent = Expression.Property(parent, keyPart);

      if (keyParts.Length > 1)
      {
        if (i == 0)
        {
          propertyType = type.GetProperty(keyPart).PropertyType;
        }
        else
        {
          propertyType = propertyType.GetProperty(keyPart).PropertyType;
        }
      }
    }

    MethodCallExpression orderByExpression = Expression.Call(
      typeof(Queryable),
      orderMethodName,
      new Type[] { type, propertyType },
      query.Expression,
      CreateExpression(type, key)
    );

    return query.Provider.CreateQuery<T>(orderByExpression);
  }
  catch (Exception e)
  {
    return query;
  }
}

The CreateExpression method which is used in my solution is defined in this post.

The usage of the OrderBy extension method is as follows.

IQueryable<Foo> q = [Your database context].Foos.AsQueryable();
IQueryable<Foo> p = null;

p = q.OrderBy("myBar.name");  // Ascending sort
// Or
p = q.OrderBy("myBar.name", false);  // Descending sort
// Materialize
var result = p.ToList();

The type Foo and its properties are also taken from the same post as method CreateExpression.

Hope you find this post helpful.

like image 115
theshinylight Avatar answered Oct 27 '22 20:10

theshinylight