/// <summary> /// Private helper to parse a sort expression string and create Comparison delegates based on each property. /// </summary> /// <param name="sortExpression">Sort expression to parse.</param> /// <returns>List of Comparison delegates, one for each property.</returns> private static List <Comparison <T> > GetFieldComparisons(String sortExpression) { SimpleTokenizer parser = new SimpleTokenizer(sortExpression); List <Comparison <T> > comparisons = new List <Comparison <T> >(4); List <String> propertyParts = new List <string>(4); Boolean moreProperties; do { do { String property = parser.ReadIdentity(); if (property.Length == 0) { throw new ParserException(parser.Position, sortExpression, "Field or property expected"); } propertyParts.Add(property); }while (parser.AdvanceIfSymbol('.')); moreProperties = parser.AdvanceIfSymbol(','); bool ascending = true; if (moreProperties == false) { if (parser.AdvanceIfIdent(DESC) || parser.AdvanceIfIdent(DESCENDING)) { ascending = false; moreProperties = parser.AdvanceIfSymbol(','); } else if (parser.AdvanceIfIdent(ASC) || parser.AdvanceIfIdent(ASCENDING)) { moreProperties = parser.AdvanceIfSymbol(','); } } try { comparisons.Add(GetPropertyComparison(propertyParts, ascending)); } catch (ArgumentException ex) { throw new ParserException(parser.Position, parser.Expression, ex.Message); } propertyParts.Clear(); }while (moreProperties); parser.ExpectEnd(); return(comparisons); }
/// <summary> /// Sorts the elements of a queryable sequence based on the given search criteria. /// </summary> /// <param name="source">The IQueryable sequence to be sorted.</param> /// <param name="sortExpression">A SQL-like sort expression with comma separated property names (and optional direction specifiers) (e.g. "Age DESC, Name.Length")</param> /// <returns>A queryable sequence sorted according to the sort expression</returns> /// <exception cref="System.ArgumentNullException">source or sortExpression is null.</exception> /// <exception cref="ParserException">if sortExpression is not properly formatted or contains unrecognized property or field names..</exception> public static IOrderedQueryable <T> OrderBy(IQueryable <T> source, String sortExpression) { if (source == null) { throw new ArgumentNullException("source"); } if (sortExpression == null) { throw new ArgumentNullException("sortExpression"); } SimpleTokenizer tokenizer = new SimpleTokenizer(sortExpression); IQueryable <T> result = source; List <String> propParts = new List <string>(4); do { ParameterExpression param = Expression.Parameter(typeof(T), "o"); // Create (nested) member access expression. Expression body = param; do { String property = tokenizer.ReadIdentity(); if (property.Length == 0) { throw new ParserException(tokenizer.Position, sortExpression, "Property or field expected."); } // Implicitely call Value for Nullable properties/fields. if (Nullable.GetUnderlyingType(body.Type) != null) { body = Expression.Property(body, "Value"); } MemberInfo member = GetMemberByName(body.Type, property); if (member == null) { throw new ParserException(tokenizer.Position, sortExpression, property + " not a public property or field."); } body = Expression.MakeMemberAccess(body, member); }while (tokenizer.AdvanceIfSymbol('.')); LambdaExpression keySelectorLambda = Expression.Lambda(body, param); bool ascending = true; if (tokenizer.AdvanceIfIdent(DESC) || tokenizer.AdvanceIfIdent(DESCENDING)) { ascending = false; } else if (tokenizer.AdvanceIfIdent(ASC) || tokenizer.AdvanceIfIdent(ASCENDING)) { ascending = true; } String queryMethod; if (result == source) { queryMethod = ascending ? "OrderBy" : "OrderByDescending"; } else { queryMethod = ascending ? "ThenBy" : "ThenByDescending"; } result = result.Provider.CreateQuery <T>(Expression.Call(typeof(Queryable), queryMethod, new Type[] { typeof(T), body.Type }, result.Expression, Expression.Quote(keySelectorLambda))); }while(tokenizer.AdvanceIfSymbol(",")); tokenizer.ExpectEnd(); return((IOrderedQueryable <T>)result); }