Dynamically build LINQ OrderBy

I have to start with a disclaimer. The content of this post is not original; in fact, while looking for the solution for my personal project, I found this jewel from the following sites:
All credits go to the respective bloggers. This post is just for me (or anyone who visit my blogger site) to see codes and samples.

First, create a OrderByHelper class as shown below.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web;

namespace com.mycompany.intranet.Helpers
{
    public static class OrderByHelper
    {
        //
        // Private data structure for this helper
        private enum SortDirection { Ascending = 0, Descending = 1 };

        private class ItemInfo
        {
            public string PropertyName { get; set; }
            public SortDirection Direction { get; set; }
            public bool IsFirst { get; set; }
        }

        public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> enumerable, string param)
        {
            // 1. Convert IEnumerable to IQueryable using AsQueryable()
            // 2. Call OrderBy (return IQuerable - see below)
            // 3. Finally, convert IQueryable to IEnumerable
            return enumerable.AsQueryable().OrderBy(param).AsEnumerable();
        }

        public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, string param)
        {
            foreach (ItemInfo item in ParseParam(param))
            {
                collection = ApplyOrderBy<T>(collection, item);
            }

            return collection;
        }

        private static IQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, ItemInfo item)
        {
            string[] _props = item.PropertyName.Split('.'); // *** ???
            Type _type = typeof(T);

            ParameterExpression _arg = Expression.Parameter(_type, "x");
            Expression _expr = _arg;
            foreach(string _prop in _props)
            {
                // use reflection
                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);
            string _methodName = string.Empty;

            if(!item.IsFirst && collection is IOrderedQueryable<T>)
            {
                if (item.Direction == SortDirection.Ascending)
                    _methodName = "ThenBy";
                else
                    _methodName = "ThenByDescending";
            }
            else
            {
                if (item.Direction == SortDirection.Ascending)
                    _methodName = "OrderBy";
                else
                    _methodName = "OrderByDescending";
            }

            // ***
            return (IOrderedQueryable<T>)typeof(Queryable).GetMethods().Single(
                _m => _m.Name == _methodName
                    && _m.IsGenericMethodDefinition
                    && _m.GetGenericArguments().Length == 2
                    && _m.GetParameters().Length == 2)
                    .MakeGenericMethod(typeof(T), _type)
                    .Invoke(null, new object[] {collection, _lambda}
                );

        }

        private static IEnumerable<ItemInfo> ParseParam(string param)
        {
            if (string.IsNullOrEmpty(param))
                yield break;        // yield used inside IEnumerable 

            string[] _items = param.Split(new char[]{ ',', ';' });
            
            bool _isFirst = true;

            foreach(string _item in _items)
            {
                string[] _pair = _item.Trim().Split(' ');

                if(_pair.Length > 2)
                    throw new ArgumentException(string.Format("Invalid OrderBy string '{0}'. Order By Format: Property [,|;] Property ASC [,|;] Property DESC", _item));

                string _prop = _pair[0].Trim();
                if (string.IsNullOrEmpty(_prop))
                    throw new ArgumentException("Invalid Property. Order By Format: Property [,|;] Property ASC [,|;] Property DESC");

                SortDirection _direction = SortDirection.Ascending;
                if (_pair.Length == 2)
                    _direction = ("desc".Equals(_pair[1].Trim(), StringComparison.OrdinalIgnoreCase)) ? SortDirection.Descending : SortDirection.Ascending;

                yield return new ItemInfo() { PropertyName = _prop, Direction = _direction, IsFirst = _isFirst };

                _isFirst = false;
            }
        }
    }
}



Here is one example that shows how to use this extension. This function generates the list of staff members who report to a supervisor (specified in supervisor_id) and returns the list in json format. It sorts the list by title and then by last name.


using com.mycompany.intranet.Helpers;

public ActionResult GetDepartmentStaff(Guid supervisor_id)
{
     var _staff = from p in db.People
                  join j1 in db.Jobs on p.Id equals j1.PersonId into pall
                  from j in pall.DefaultIfEmpty()
                  where j.ReportToPersonId == supervisor_id
                  where j.IsPrimary == true
                  where p.IsActive == true
                  select new
                         {
                             id = p.Id,
                             firstname = p.FirstName,
                             lastname = p.LastName,
                             title = p.Alias ?? p.JobTitle,
                             photo_url = @"/photos/" + p.PhotoFileName,
                             employee_id = p.EmployeeId.ToString(),
                             tel = p.Tel,
                             cell = p.Cell,
                             fax = p.Fax,
                             email = p.Email,
                             building = p.Bulding,
                             room = p.Room
                         };

    return Json(_staff.OrderBy("title ASC;lastname ASC").ToList(),JsonRequestBehavior.AllowGet);

}

Comments

Popular Posts