public static IQueryable <T> OrderBy <T>(this IQueryable <T> query, string orderBySpec, IDictionary <string, string> nameMapping = null) { if (string.IsNullOrWhiteSpace(orderBySpec)) { return(query); } var memberInfos = typeof(T).GetAllProperties(); string propRef; bool isDesc; var segments = orderBySpec.SplitNames(); foreach (var segm in segments) { if (string.IsNullOrWhiteSpace(segm)) { continue; } var arr = segm.SplitNames(':', '-'); // '-' is better for URLs, ':' is a special symbol, must be escaped if (arr.Length < 2) { propRef = segm; isDesc = false; } else { propRef = arr[0]; var ascDesc = arr[1].Trim().ToUpper(); if (!string.IsNullOrEmpty(ascDesc)) { Util.Check(ascDesc == "ASC" || ascDesc == "DESC", "Invalid OrderBy spec, ASC/DESC flag: '{0}'", ascDesc); } isDesc = ascDesc == "DESC"; } Util.Check(!string.IsNullOrWhiteSpace(propRef), "Invalid OrderBy spec, empty property ref: '{0}'", orderBySpec); //Build expression ent => ent.Prop string mappedName; if (nameMapping != null && nameMapping.TryGetValue(propRef, out mappedName)) { propRef = mappedName; } //propRef might be a chain of reads: User.Person.FirstName; let's unpack it and build expression var prm = Expression.Parameter(typeof(T), "@ent"); var memberGet = ExpressionHelper.BuildChainedPropReader(prm, propRef); var lambda = Expression.Lambda <Func <T, object> >(memberGet, prm); // Note: we might need to use ThenBy and ThenByDescending here for all clauses after first. // But it looks like it works OK, at least in SQL, the ORDER BY clause is correct query = isDesc ? query.OrderByDescending(lambda) : query.OrderBy(lambda); } return(query); }//method