1

I have an entity called User in my EF Model:

public class User
{
    public int UserId { get; set; }
    public Branch HomeLocation{ get; set; }   
    public string CellPhone { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public string Email { get; set; } 
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserCode { get; set; }
}

Branch is another entity in the model:

public class Branch
{    
    public int BranchId { get; set; }
    public string BranchName{ get; set; }
    public string Address { get; set; } 
}

My requirement is to get a users list and display it on a grid, and then sort the list by some of the columns (one at a time). Say, for example, sort by username, firstname, lastname and HomeLocation. When sorting by homelocation, it should be sorted by the branch name.

I have many grids like this displaying other data as well. So I want to develop a generic sort mechanism and I have achieved it using some of the examples found in Google, for example this one:

public class GenericSorter<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> source, string sortBy, string sortDirection)
    {
        var param = Expression.Parameter(typeof(T), "item");

        var sortExpression = Expression.Lambda<Func<T, object>>
            (Expression.Convert(Expression.Property(param, sortBy), typeof(object)), param);

        switch (sortDirection.ToLower())
        {
            case "asc":
                return source.AsQueryable<T>().OrderBy<T, object>(sortExpression);
            default:
                return source.AsQueryable<T>().OrderByDescending<T, object>(sortExpression);

        } 
    }
}

However, sorting by home location fails because it needs to be sorted by an inner property of the user entity. I've tried using Dynamic LINQ library too, but there's no luck.

Update: Note that I have to sort a List, not IQueryable, because my list contains fields encrypted with AE, which don't support DB-level sorting.

Can someone point out to me how to achieve the dynamic sorting from an inner property?

Update2: I followed the example and implemented the sort using the extension methods and this is how it's applied on my list:

var users = (from u in context.Users.Include("Branch")
                    where (u.FkBranchId == branchId || branchId == -1) && u.IsActive
                        && (searchTerm == string.Empty || (u.FirstName.Contains(searchTerm) || u.LastName.Equals(searchTerm)
                            || u.UserName.Contains(searchTerm) || u.UserCode.Contains(searchTerm)))
                    select u).ToList();

        var rowCount = users.Count;

        var orderedList = users.OrderBy(sortInfo.SortColumn).Skip(pageInfo.Skip).Take(pageInfo.PageSize).ToList();

But I get the following error: Object of type 'System.Linq.Expressions.Expression1[System.Func2[ClientData.User,System.String]]' cannot be converted to type 'System.Func`2[ClientData.User,System.String]'.

Error is thrown from the following:

object result = typeof(Enumerable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda });

After this, I'm receiving the following error, in some occasions as explained in the comment: enter image description here

devC
  • 1,384
  • 5
  • 32
  • 56
  • Why do this in Linq, let the grid handle the sorting for you? – DavidG May 23 '19 at 10:05
  • Well, this is a legacy application using ASP.NET GridView and we are sort of re-writing with minimal changes to the existing implementation – devC May 23 '19 at 10:07
  • 1
    Have you already checked this one ? https://stackoverflow.com/a/233505/3470596 – dbraillon May 23 '19 at 10:30
  • Nope, I will try that out, thanks. – devC May 23 '19 at 10:33
  • @dbraillon: so there's a limitation in using that. In the said post, the sorting is done on IQueryable, which doesn't work for me, because I have sort fields encrypted with AE which doesn't support DB level sorting. So I have to first convert them to a List and then sort - using a generic sort. – devC May 24 '19 at 08:27
  • 1
    If I understood you well, just change Queryable by Enumerable in the code and you should be ok. – dbraillon May 24 '19 at 08:45

1 Answers1

2

Adapt the code from @MarcGravell found here.

public static class EnumerableExtensions
{
    public static IOrderedEnumerable<T> OrderBy<T>(
        this IEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "OrderBy");
    }

    public static IOrderedEnumerable<T> OrderByDescending<T>(
        this IEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "OrderByDescending");
    }

    public static IOrderedEnumerable<T> ThenBy<T>(
        this IOrderedEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "ThenBy");
    }

    public static IOrderedEnumerable<T> ThenByDescending<T>(
        this IOrderedEnumerable<T> source,
        string property)
    {
        return ApplyOrder<T>(source, property, "ThenByDescending");
    }

    static IOrderedEnumerable<T> ApplyOrder<T>(
        IEnumerable<T> source,
        string property,
        string methodName)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

        object result = typeof(Enumerable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda.Compile() });
        return (IOrderedEnumerable<T>)result;
    }
}

UPDATED

Use it from a List<> :

var list = new List<MyModel>();
list = list.OrderBy("MyProperty");
dbraillon
  • 1,742
  • 2
  • 22
  • 34
  • How do I access this OrderBy outside of the implementation class? In that example, the implementation is in Program class, and accessed within the Main() method. – devC May 24 '19 at 09:32
  • Put it in an extension class, please see my updated answer – dbraillon May 24 '19 at 10:00
  • I updated the question with another error I get after applying the given solution. – devC May 24 '19 at 11:08
  • 1
    Oh yeah, that's because it is encapsulated inside an Expression, try to compile it. `.Invoke(null, new object[] { source, lambda.Compile() });`, I will update my answer. – dbraillon May 24 '19 at 11:43
  • Thanks, it's good so far, but I'm getting a null reference exception in certain instances, for example - when a sort column has empty or null values. The source of the error is 'Anonymously Hosted DynamicMethods Assembly'. The screenshot attached in the original thread. – devC May 27 '19 at 06:14
  • Is there anyway I can still sort with say user.Branch.BranchName, when for some users Branch would be null? I came across https://stackoverflow.com/questions/34927146/order-by-in-listt-with-lambda-expression-when-foreign-key-is-null, but not sure how to apply it. – devC May 28 '19 at 06:20
  • You should take a look at this: https://stackoverflow.com/a/17650708/3470596 that is exactly what you need – dbraillon May 28 '19 at 07:13